packages feed

unleash-client-haskell (empty) → 0.4.7

raw patch · 6 files changed

+632/−0 lines, 6 filesdep +aesondep +basedep +containers

Dependencies added: aeson, base, containers, http-client-tls, http-media, mtl, record-dot-preprocessor, record-hasfield, servant, servant-client, text, time, unleash-client-haskell, unleash-client-haskell-core, unliftio

Files

+ CHANGELOG.md view
@@ -0,0 +1,116 @@+## <small>0.4.7 (2023-09-14)</small>++* Add CHANGELOG.md ([8bfd48b](https://github.com/finn-no/unleash-client-haskell/commit/8bfd48b))+* Don't do -O2 ([eb8850b](https://github.com/finn-no/unleash-client-haskell/commit/eb8850b))+* Set version to 0.4.7 ([20b23d9](https://github.com/finn-no/unleash-client-haskell/commit/20b23d9))+* unleash-client-haskell-core: 0.8.9 ([997d8e3](https://github.com/finn-no/unleash-client-haskell/commit/997d8e3))++++## <small>0.4.6 (2023-09-14)</small>++* Downgrade Cabal to 3.0 ([cf24d8b](https://github.com/finn-no/unleash-client-haskell/commit/cf24d8b))+* Set version to 0.4.6 ([8c813a9](https://github.com/finn-no/unleash-client-haskell/commit/8c813a9))+* unleash-client-haskell-core: 0.8.7 ([4ae98c3](https://github.com/finn-no/unleash-client-haskell/commit/4ae98c3))+* Version bound all deps ([d518273](https://github.com/finn-no/unleash-client-haskell/commit/d518273))++++## <small>0.4.5 (2023-09-13)</small>++* Add some more package properties ([5042c05](https://github.com/finn-no/unleash-client-haskell/commit/5042c05))+* Set version to 0.4.5 ([4cf0364](https://github.com/finn-no/unleash-client-haskell/commit/4cf0364))+* unleash-client-haskell-core: 0.8.6 ([33571e5](https://github.com/finn-no/unleash-client-haskell/commit/33571e5))++++## <small>0.4.4 (2023-02-27)</small>++* Set version to 0.4.4 ([a064750](https://github.com/finn-no/unleash-client-haskell/commit/a064750))++++## <small>0.4.3 (2023-02-27)</small>++* Set version to 0.8.5 ([4d4aec2](https://github.com/finn-no/unleash-client-haskell/commit/4d4aec2))+* unleash-client-haskell-core: 0.8.5 ([b82fffa](https://github.com/finn-no/unleash-client-haskell/commit/b82fffa))++++## <small>0.4.2 (2023-02-27)</small>++* Build with -haddock ([8433559](https://github.com/finn-no/unleash-client-haskell/commit/8433559))+* Set version to 0.4.2 ([822623a](https://github.com/finn-no/unleash-client-haskell/commit/822623a))++++## <small>0.4.1 (2023-02-06)</small>++* Add Haddock docs ([f909f98](https://github.com/finn-no/unleash-client-haskell/commit/f909f98))+* Set version to 0.4.1 ([8bbfd00](https://github.com/finn-no/unleash-client-haskell/commit/8bbfd00))+* Update README.md ([4121c0e](https://github.com/finn-no/unleash-client-haskell/commit/4121c0e))+* Update README.md ([d68a358](https://github.com/finn-no/unleash-client-haskell/commit/d68a358))+* Update README.md ([cf91866](https://github.com/finn-no/unleash-client-haskell/commit/cf91866))+* Update README.md ([1766f05](https://github.com/finn-no/unleash-client-haskell/commit/1766f05))+* Update unleash-haskell-client-core to 0.8.2 ([1c73d01](https://github.com/finn-no/unleash-client-haskell/commit/1c73d01))+* Update unleash-haskell-client-core to 0.8.4 ([39904a0](https://github.com/finn-no/unleash-client-haskell/commit/39904a0))++++## 0.4.0 (2023-01-27)++* Re-export some constructors from unleash-client-haskell-core ([dd0d7f5](https://github.com/finn-no/unleash-client-haskell/commit/dd0d7f5))+* Set to version 0.4.0 ([aef1428](https://github.com/finn-no/unleash-client-haskell/commit/aef1428))+* Update README.md ([2c94f80](https://github.com/finn-no/unleash-client-haskell/commit/2c94f80))+* Update README.md ([77a8c4c](https://github.com/finn-no/unleash-client-haskell/commit/77a8c4c))+* Update README.md ([9e9efbd](https://github.com/finn-no/unleash-client-haskell/commit/9e9efbd))++++## <small>0.3.1 (2023-01-26)</small>++* Move HttpClient from unleash-client-haskell-core ([7d85ac5](https://github.com/finn-no/unleash-client-haskell/commit/7d85ac5))+* Refactor ([2a34192](https://github.com/finn-no/unleash-client-haskell/commit/2a34192))+* Refactor HttpClient ([fbf5957](https://github.com/finn-no/unleash-client-haskell/commit/fbf5957))+* Set version to 0.3.1 ([319428c](https://github.com/finn-no/unleash-client-haskell/commit/319428c))+* Support TLS by default ([f596f51](https://github.com/finn-no/unleash-client-haskell/commit/f596f51))+* Update README.md ([8dcc937](https://github.com/finn-no/unleash-client-haskell/commit/8dcc937))+* Update README.md ([0e2d998](https://github.com/finn-no/unleash-client-haskell/commit/0e2d998))++++## 0.3.0 (2023-01-26)++* Set to version 0.3.0 ([24dcdfd](https://github.com/finn-no/unleash-client-haskell/commit/24dcdfd))+* Use MonadReader ([4a4e8d6](https://github.com/finn-no/unleash-client-haskell/commit/4a4e8d6))++++## 0.2.0 (2023-01-25)++* Add apiKey to Config ([32d43c3](https://github.com/finn-no/unleash-client-haskell/commit/32d43c3))+* Set to version 0.2.0 ([e225ea6](https://github.com/finn-no/unleash-client-haskell/commit/e225ea6))+* Example: Die if registration fails ([8e5e021](https://github.com/finn-no/unleash-client-haskell/commit/8e5e021))++++## <small>0.1.1 (2023-01-25)</small>++* Set to version 0.1.1 ([6ec7b50](https://github.com/finn-no/unleash-client-haskell/commit/6ec7b50))+* Use unleash-client-haskell-core 0.7.0 ([6071600](https://github.com/finn-no/unleash-client-haskell/commit/6071600))++++## 0.1.0 (2023-01-25)++* Add variant getters ([ffddec4](https://github.com/finn-no/unleash-client-haskell/commit/ffddec4))+* Initial commit ([32df140](https://github.com/finn-no/unleash-client-haskell/commit/32df140))+* Misc. cleanup ([67eaac3](https://github.com/finn-no/unleash-client-haskell/commit/67eaac3))+* Update and add comments ([255096d](https://github.com/finn-no/unleash-client-haskell/commit/255096d))+* Update comments ([f31eda2](https://github.com/finn-no/unleash-client-haskell/commit/f31eda2))+* Update README.md ([a39d5a2](https://github.com/finn-no/unleash-client-haskell/commit/a39d5a2))+* Update README.md ([2d0601c](https://github.com/finn-no/unleash-client-haskell/commit/2d0601c))+* Update README.md ([0885486](https://github.com/finn-no/unleash-client-haskell/commit/0885486))+* Update unleash-client-haskell-core ([4f1f503](https://github.com/finn-no/unleash-client-haskell/commit/4f1f503))+* Use record-dot-preprocessor ([fd68143](https://github.com/finn-no/unleash-client-haskell/commit/fd68143))+* Use unleash-client-haskell-core at 0.6.5 tag ([a8d2040](https://github.com/finn-no/unleash-client-haskell/commit/a8d2040))
+ README.md view
@@ -0,0 +1,41 @@+# unleash-client-haskell++This is an [Unleash](https://www.getunleash.io/) client SDK for Haskell.++`Unleash.Client` provides a stateful HTTP client with functions and types for:++- Client configuration+- Client registration+- Fetching feature toggles+- Fetching variants+- Sending metrics++You'll need to spawn threads and handle errors yourself. This is demonstrated in the [example application](example/Main.hs).++`unleash-client-haskell` is a (working) work in progress and the API is likely to change.++See [unleash-client-haskell-core](https://github.com/finn-no/unleash-client-haskell-core) for a bare-bones feature toggle evaluation library.++## Build++```+nix-build+```++## Maintainers++- [Even Brenden](mailto:uch@anythingexternal.com)+- [Eirik Meland](mailto:eirik.meland@gmail.com)++## Dependencies++- aeson (BSD-3-Clause)+- containers (BSD-3-Clause)+- http-client-tls (MIT)+- http-media (MIT)+- servant (BSD-3-Clause)+- servant-client (BSD-3-Clause)+- text (BSD-2-Clause)+- time (BSD-2-Clause)+- unleash-client-haskell-core (MIT)+- unliftio (MIT)
+ example/Main.hs view
@@ -0,0 +1,92 @@+{- |+Module      : Main+Copyright   : Copyright © FINN.no AS, Inc. All rights reserved.+License     : MIT+Stability   : experimental++Example application that uses unleash-client-haskell. Spawns a state poller thread that updates the feature toggles, a metrics sender thread, and an application that continuously reads a feature toggle. The application will block until the first feature toggle set is received.+-}+module Main where++import Control.Concurrent (threadDelay)+import Control.Monad (forever)+import Control.Monad.Reader (ReaderT (runReaderT))+import Control.Monad.Reader.Class (asks)+import Data.Foldable (traverse_)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Void (Void)+import Servant.Client (BaseUrl (BaseUrl), Scheme (Http))+import System.Exit (die)+import Unleash (emptyContext)+import Unleash.Client (+    HasUnleash (..),+    UnleashConfig (..),+    isEnabled,+    makeUnleashConfig,+    pollToggles,+    pushMetrics,+    registerClient,+ )+import UnliftIO++unleashServer :: BaseUrl+unleashServer = BaseUrl Http "your-unleash-server" 80 mempty++secretKey :: Maybe Text+secretKey = Nothing++featureToggle :: Text+featureToggle = "your-feature-toggle"++type Program a = ReaderT AppConfig IO a++data AppConfig = AppConfig {unleashConfig :: UnleashConfig}++instance HasUnleash AppConfig where+    getUnleashConfig = unleashConfig++main :: IO ()+main = do+    config <- makeUnleashConfig "unleash-client-haskell-example" "localhost" unleashServer secretKey+    runReaderT program (AppConfig config)++program :: Program ()+program = do+    registerApplication+    let threads = [statePoller, metricsPusher, application]+    runConcurrently $ traverse_ Concurrently threads++application :: Program Void+application =+    forever do+        enabled <- isEnabled featureToggle emptyContext+        liftIO . putStrLn $ T.unpack featureToggle <> " is " <> (if enabled then "enabled" else "disabled")+        liftIO . threadDelay $ 2 * 1000 * 1000++registerApplication :: Program ()+registerApplication = do+    registerClient+        >>= liftIO . \case+            Left error -> die $ "Could not register application (" <> show error <> ")"+            Right _ -> putStrLn "Application registered"++statePoller :: Program Void+statePoller = do+    config <- asks getUnleashConfig+    forever do+        pollToggles+            >>= liftIO . \case+                Left error -> putStrLn $ "Could not get state (" <> show error <> ")"+                Right _ -> putStrLn "State received"+        liftIO . threadDelay $ config.statePollIntervalInSeconds * 1000 * 1000++metricsPusher :: Program Void+metricsPusher = do+    config <- asks getUnleashConfig+    forever do+        liftIO . threadDelay $ config.metricsPushIntervalInSeconds * 1000 * 1000+        pushMetrics+            >>= liftIO . \case+                Left error -> putStrLn $ "Could not send metrics (" <> show error <> ")"+                Right _ -> putStrLn "Metrics sent"
+ src/Unleash/Client.hs view
@@ -0,0 +1,205 @@+{- |+Module      : Unleash.Client+Copyright   : Copyright © FINN.no AS, Inc. All rights reserved.+License     : MIT+Stability   : experimental++Functions and types that constitute an Unleash client SDK.++This module re-exports select constructors from [unleash-client-haskell-core](https://github.com/finn-no/unleash-client-haskell-core).+-}+module Unleash.Client (+    makeUnleashConfig,+    UnleashConfig (..),+    HasUnleash (..),+    registerClient,+    pollToggles,+    pushMetrics,+    isEnabled,+    tryIsEnabled,+    getVariant,+    tryGetVariant,+    -- Re-exports+    Context (..),+    emptyContext,+    VariantResponse (..),+) where++import Control.Concurrent.MVar+import Control.Monad (unless, void)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Control.Monad.Reader (MonadReader, asks)+import Data.Text (Text)+import Data.Time (UTCTime, getCurrentTime)+import Network.HTTP.Client.TLS (newTlsManager)+import Servant.Client (BaseUrl, ClientEnv, ClientError, mkClientEnv)+import Unleash (Context (..), Features, MetricsPayload (..), RegisterPayload (..), VariantResponse (..), emptyContext, emptyVariantResponse, featureGetVariant, featureIsEnabled)+import Unleash.Internal.HttpClient (getAllClientFeatures, register, sendMetrics)++-- | Smart constructor for Unleash client configuration. Initializes the mutable variables properly.+makeUnleashConfig ::+    MonadIO m =>+    -- | Application name.+    Text ->+    -- | Instance identifier.+    Text ->+    -- | Unleash server base URL.+    BaseUrl ->+    -- | API key for authorization.+    Maybe Text ->+    -- | Configuration instance.+    m UnleashConfig+makeUnleashConfig applicationName instanceId serverUrl apiKey = do+    state <- liftIO newEmptyMVar+    metrics <- liftIO $ newMVar mempty+    metricsBucketStart <- liftIO $ getCurrentTime >>= newMVar+    manager <- newTlsManager+    let clientEnv = mkClientEnv manager serverUrl+    pure $+        UnleashConfig+            { applicationName = applicationName,+              instanceId = instanceId,+              state = state,+              statePollIntervalInSeconds = 4,+              metrics = metrics,+              metricsBucketStart = metricsBucketStart,+              metricsPushIntervalInSeconds = 8,+              apiKey = apiKey,+              httpClientEnvironment = clientEnv+            }++-- | Unleash client configuration. Use the smart constructor or make sure the mutable metrics variables are not empty.+data UnleashConfig = UnleashConfig+    { -- | Application name.+      applicationName :: Text,+      -- | Instance identifier.+      instanceId :: Text,+      -- | Full client feature set state.+      state :: MVar Features,+      -- | Feature set state update interval.+      statePollIntervalInSeconds :: Int,+      -- | Collected metrics state.+      metrics :: MVar [(Text, Bool)],+      -- | Current metrics bucket start time.+      metricsBucketStart :: MVar UTCTime,+      -- | Metrics sending interval.+      metricsPushIntervalInSeconds :: Int,+      -- | API key for authorization.+      apiKey :: Maybe Text,+      -- | HTTP client environment.+      httpClientEnvironment :: ClientEnv+    }++-- | Reader monad convenience class. Use this to get an Unleash configuration from inside of an application configuration (for example).+class HasUnleash r where+    getUnleashConfig :: r -> UnleashConfig++-- | Register client for the Unleash server. Call this on application startup before calling the state poller and metrics pusher functions.+registerClient :: (HasUnleash r, MonadReader r m, MonadIO m) => m (Either ClientError ())+registerClient = do+    config <- asks getUnleashConfig+    now <- liftIO getCurrentTime+    let registrationPayload :: RegisterPayload+        registrationPayload =+            RegisterPayload+                { appName = config.applicationName,+                  instanceId = config.instanceId,+                  started = now,+                  intervalSeconds = config.metricsPushIntervalInSeconds+                }+    void <$> register config.httpClientEnvironment config.apiKey registrationPayload++-- | Fetch the most recent feature toggle set from the Unleash server. Meant to be run every statePollIntervalInSeconds. Non-blocking.+pollToggles :: (HasUnleash r, MonadReader r m, MonadIO m) => m (Either ClientError ())+pollToggles = do+    config <- asks getUnleashConfig+    eitherFeatures <- getAllClientFeatures config.httpClientEnvironment config.apiKey+    either (const $ pure ()) (updateState config.state) eitherFeatures+    pure . void $ eitherFeatures+    where+        updateState state value = do+            isUpdated <- liftIO $ tryPutMVar state value+            liftIO . unless isUpdated . void $ swapMVar state value++-- | Push metrics to the Unleash server. Meant to be run every metricsPushIntervalInSeconds. Blocks if the mutable metrics variables are empty.+pushMetrics :: (HasUnleash r, MonadReader r m, MonadIO m) => m (Either ClientError ())+pushMetrics = do+    config <- asks getUnleashConfig+    now <- liftIO getCurrentTime+    lastBucketStart <- liftIO $ swapMVar config.metricsBucketStart now+    bucket <- liftIO $ swapMVar config.metrics mempty+    let metricsPayload =+            MetricsPayload+                { appName = config.applicationName,+                  instanceId = config.instanceId,+                  start = lastBucketStart,+                  stop = now,+                  toggles = bucket+                }+    void <$> sendMetrics config.httpClientEnvironment config.apiKey metricsPayload++-- | Check if a feature is enabled or not. Blocks until first feature toggle set is received. Blocks if the mutable metrics variables are empty.+isEnabled ::+    (HasUnleash r, MonadReader r m, MonadIO m) =>+    -- | Feature toggle name.+    Text ->+    -- | Client context.+    Context ->+    -- | Whether or not the feature toggle is enabled.+    m Bool+isEnabled feature context = do+    config <- asks getUnleashConfig+    state <- liftIO . readMVar $ config.state+    enabled <- featureIsEnabled state feature context+    liftIO $ modifyMVar_ config.metrics (\info -> pure $ (feature, enabled) : info)+    pure enabled++-- | Check if a feature is enabled or not. Returns false for all toggles until first toggle set is received. Blocks if the mutable metrics variables are empty.+tryIsEnabled ::+    (HasUnleash r, MonadReader r m, MonadIO m) =>+    -- | Feature toggle name.+    Text ->+    -- | Client context.+    Context ->+    -- | Whether or not the feature toggle is enabled.+    m Bool+tryIsEnabled feature context = do+    config <- asks getUnleashConfig+    maybeState <- liftIO . tryReadMVar $ config.state+    case maybeState of+        Just state -> do+            enabled <- featureIsEnabled state feature context+            liftIO $ modifyMVar_ config.metrics (\info -> pure $ (feature, enabled) : info)+            pure enabled+        Nothing -> pure False++-- | Get a variant. Blocks until first feature toggle set is received.+getVariant ::+    (HasUnleash r, MonadReader r m, MonadIO m) =>+    -- | Feature toggle name.+    Text ->+    -- | Client context.+    Context ->+    -- | Variant.+    m VariantResponse+getVariant feature context = do+    config <- asks getUnleashConfig+    state <- liftIO . readMVar $ config.state+    featureGetVariant state feature context++-- | Get a variant. Returns an empty variant until first toggle set is received.+tryGetVariant ::+    (HasUnleash r, MonadReader r m, MonadIO m) =>+    -- | Feature toggle name.+    Text ->+    -- | Client context.+    Context ->+    -- | Variant.+    m VariantResponse+tryGetVariant feature context = do+    config <- asks getUnleashConfig+    maybeState <- liftIO . tryReadMVar $ config.state+    case maybeState of+        Just state -> do+            featureGetVariant state feature context+        Nothing -> pure emptyVariantResponse
+ src/Unleash/Internal/HttpClient.hs view
@@ -0,0 +1,95 @@+{- |+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
+ unleash-client-haskell.cabal view
@@ -0,0 +1,83 @@+cabal-version: 3.0++name: unleash-client-haskell+version: 0.4.7+synopsis: Unleash feature toggle client+description:+    This is an [Unleash](https://www.getunleash.io/) client SDK for Haskell.++    'Unleash.Client' provides a stateful HTTP client with functions and types for:++        - Client configuration+        - Client registration+        - Fetching feature toggles+        - Fetching variants+        - Sending metrics++    See [the source repo](https://github.com/finn-no/unleash-client-haskell) for more information and example usage.+category: Web+author: FINN.no+homepage: https://github.com/finn-no/unleash-client-haskell+maintainer: See README.md+license: MIT+copyright: Copyright © FINN.no AS, Inc. All rights reserved.+extra-doc-files: CHANGELOG.md+extra-source-files: README.md++common all+    build-depends:+        base >=4.7 && <5,+        record-dot-preprocessor >= 0.2.15 && < 0.3,+        record-hasfield >= 1.0 && < 1.1+    default-extensions:+        BlockArguments+        DeriveFoldable+        DeriveFunctor+        DeriveTraversable+        DerivingStrategies+        LambdaCase+        OverloadedStrings+        RankNTypes+        ScopedTypeVariables+    ghc-options:+        -W+        -Wno-unused-top-binds+        -Wredundant-constraints+        -Wunused-packages+        -haddock+        -F -pgmF=record-dot-preprocessor+    default-language: Haskell2010++library+    import: all+    exposed-modules:+        Unleash.Client,+    other-modules:+        Unleash.Internal.HttpClient+        Paths_unleash_client_haskell+    autogen-modules:+        Paths_unleash_client_haskell+    hs-source-dirs: src+    build-depends:+        aeson >= 2.0.3 && < 2.1,+        containers >= 0.6.4 && < 0.7,+        http-client-tls >= 0.3.6 && < 0.4,+        http-media >= 0.8.0 && < 0.9,+        mtl >= 2.2.2 && < 2.3,+        servant >= 0.19.1 && < 0.20,+        servant-client >= 0.19 && < 0.20,+        text >= 1.2.5 && < 1.3,+        time >= 1.9.3 && < 1.10,+        unleash-client-haskell-core >= 0.8.9 && < 0.9++executable example+    import: all+    main-is: Main.hs+    hs-source-dirs: example+    build-depends:+        mtl,+        servant-client,+        text,+        unleash-client-haskell,+        unleash-client-haskell-core,+        unliftio >= 0.2.23 && < 0.3