content: {-
    Copyright (c) Meta Platforms, Inc. and affiliates.
    All rights reserved.
  
    This source code is licensed under the BSD-style license found in the
    LICENSE file in the root directory of this source tree.
  -}
  
  {-# LANGUAGE AllowAmbiguousTypes, TypeFamilyDependencies #-}
  {-# OPTIONS_GHC -Wno-star-is-type #-}
  module Glean.Util.ConfigProvider
    ( ConfigProvider(..)
    , ConfigOptions
    , Deserializer
    , ConfigPath
    , TestConfigProvider(..)
    , withConfigOptions
  
    -- * The empty config provider that always fails
    , NullConfigProvider
    , NullConfigProviderOptions
    ) where
  
  import Control.Exception
  import Data.ByteString (ByteString)
  import Data.Text (Text)
  import Data.Typeable
  import Options.Applicative (Parser, ParserInfo(..))
  
  import Glean.Init
  
  type Deserializer a = ByteString -> Either String a
  
  type ConfigPath = Text
  
  type family ConfigOptions cfg = opts | opts -> cfg
  
  -- | An interface for a provider of configuration values that might
  -- change over time.
  class ConfigProvider cfg where
  
    -- | Allow the user to set the config options
    configOptions :: Parser (ConfigOptions cfg)
  
    -- | A default set of options
    defaultConfigOptions :: ConfigOptions cfg
  
    -- | Create a ConfigProvider
    withConfigProvider :: ConfigOptions cfg -> (cfg -> IO a) -> IO a
  
    type Subscription cfg :: * -> *
  
    -- | subscribe to a configuration value. The callback should be
    -- invoked whenever the underlying value changes.
    subscribe
      :: Typeable a
      => cfg
      -> ConfigPath
      -> (a -> IO ())
      -> Deserializer a
      -> IO (Subscription cfg a)
  
    -- | Stop subscribing to a config
    cancel :: cfg -> Subscription cfg a -> IO ()
  
    -- | Fetch a configuration value and deserialize it.
    get :: Typeable a => cfg -> ConfigPath -> Deserializer a -> IO a
  
    -- | Identifies exceptions thrown by 'get' or 'subscribe'
    isConfigFailure :: cfg -> SomeException -> Bool
  
  
  -- | A ConfigProvider in which we can programmatically set values, for testing.
  class ConfigProvider cfg => TestConfigProvider cfg where
    setTestConfig :: cfg -> ConfigPath -> ByteString -> IO ()
  
  -- | Variant of 'withOptions' that adds in the ConfigProvider options
  withConfigOptions
    :: ConfigProvider cfg
    => ParserInfo a
    -> ((a, ConfigOptions cfg) -> IO b)
    -> IO b
  withConfigOptions parserInfo fn =
    Glean.Init.withOptions
      parserInfo {
        infoParser = (,) <$> infoParser parserInfo <*> configOptions
      }
      fn
  
  -- -----------------------------------------------------------------------------
  -- The empty config provider that always fails.
  --
  -- We use this for unit tests to ensure that we're not accidentally
  -- pulling in configs from the environment.
  
  data NullConfigProvider = NullConfigProvider
  data NullConfigProviderOptions = NullConfigProviderOptions
  data NullConfigSubscription a
  
  type instance ConfigOptions NullConfigProvider = NullConfigProviderOptions
  
  instance ConfigProvider NullConfigProvider where
    configOptions = pure NullConfigProviderOptions
    defaultConfigOptions = NullConfigProviderOptions
    withConfigProvider _ f = f NullConfigProvider
    type Subscription NullConfigProvider = NullConfigSubscription
    subscribe _ _ _ _ = throwIO $ ErrorCall "NullConfigProvider: subscribe"
    cancel _ _ = return ()
    get _ _ _ = throwIO $ ErrorCall "NullConfigProvider: get"
    isConfigFailure _ _ = True
  