-- |
-- Copyright: (c) 2019 Lucas David Traverso
-- License: MPL-2.0
-- Maintainer: Lucas David Traverso <lucas6246@gmail.com>
-- Stability: stable
-- Portability: portable
--
-- Public and stable API for the most basic usage of this library
module Conferer
  (

  -- * How to use this doc
  -- | This doc is mostly for reference, so you probably won't learn how to
  --   use conferer by reading it. For more detailed and guided documentation
  --   the best place is the webpage: <https://conferer.ludat.io/docs>

  -- * Creating a Config
  mkConfig
  , mkConfig'
  -- * Getting values from a config
  -- | These functions allow you to get any type that implements 'FromConfig'
  , fetch
  , fetch'
  , fetchKey
  , fetchFromConfig
  , safeFetchKey
  , unsafeFetchKey
  , DefaultConfig(..)
  , FromConfig
  -- * Some useful types
  , Config
  , Key
  ) where

import Data.Text (Text)
import Data.Typeable (Typeable)

import Conferer.Config.Internal
import Conferer.Config.Internal.Types
import Conferer.FromConfig.Internal
import Conferer.Key
import qualified Conferer.Source.Env as Env
import qualified Conferer.Source.CLIArgs as Cli
import qualified Conferer.Source.PropertiesFile as PropertiesFile
import Conferer.Config (Defaults)

-- | Use the 'FromConfig' instance to get a value of type @a@ from the config
--   using some default fallback. The most common use for this is creating a custom
--   record and using this function to fetch it at initialization time.
--
--   This function throws only parsing exceptions when the values are present
--   but malformed somehow (@"abc"@ as an Int) but that depends on the 'FromConfig'
--   implementation for the type.
fetch :: forall a. (FromConfig a, Typeable a, DefaultConfig a) => Config -> IO a
fetch :: Config -> IO a
fetch Config
c = Config -> a -> IO a
forall a. (Typeable a, FromConfig a) => Config -> a -> IO a
fetchFromRootConfigWithDefault Config
c a
forall a. DefaultConfig a => a
configDef

-- | Same as 'fetch' but it accepts the default as a parameter instead of using
--   the default from 'configDef'
fetch' :: forall a. (FromConfig a, Typeable a) => Config -> a -> IO a
fetch' :: Config -> a -> IO a
fetch' = Config -> a -> IO a
forall a. (Typeable a, FromConfig a) => Config -> a -> IO a
fetchFromRootConfigWithDefault

-- | Same as 'fetch'' but you can specify a 'Key' instead of the root key which allows
--   you to fetch smaller values when you need them instead of a big one at
--   initialization time.
fetchKey :: forall a. (FromConfig a, Typeable a) => Config -> Key -> a -> IO a
fetchKey :: Config -> Key -> a -> IO a
fetchKey = Config -> Key -> a -> IO a
forall a. (Typeable a, FromConfig a) => Config -> Key -> a -> IO a
fetchFromConfigWithDefault

-- | Same as 'fetchKey' but it returns a 'Nothing' when the value isn't present
safeFetchKey :: forall a. (FromConfig a, Typeable a) => Config -> Key -> IO (Maybe a)
safeFetchKey :: Config -> Key -> IO (Maybe a)
safeFetchKey Config
c Key
k = Key -> Config -> IO (Maybe a)
forall a. (FromConfig a, Typeable a) => Key -> Config -> IO a
fetchFromConfig Key
k Config
c

-- | Same as 'fetchKey' but it throws when the value isn't present.
unsafeFetchKey :: forall a. (FromConfig a, Typeable a) => Config -> Key -> IO a
unsafeFetchKey :: Config -> Key -> IO a
unsafeFetchKey Config
c Key
k = Key -> Config -> IO a
forall a. (FromConfig a, Typeable a) => Key -> Config -> IO a
fetchFromConfig Key
k Config
c


-- | Create a 'Config' which reads from command line arguments, env vars and
--   property files that depend on the environment (@config/development.properties@)
--   by default
mkConfig :: Text -> IO Config
mkConfig :: Text -> IO Config
mkConfig Text
appName =
  Config -> IO Config
forall (f :: * -> *) a. Applicative f => a -> f a
pure Config
emptyConfig
  IO Config -> (Config -> IO Config) -> IO Config
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= SourceCreator -> Config -> IO Config
addSource (SourceCreator
Cli.fromConfig)
  IO Config -> (Config -> IO Config) -> IO Config
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= SourceCreator -> Config -> IO Config
addSource (Text -> SourceCreator
Env.fromConfig Text
appName)
  IO Config -> (Config -> IO Config) -> IO Config
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= SourceCreator -> Config -> IO Config
addSource (Key -> SourceCreator
PropertiesFile.fromConfig Key
"config.file")

-- | Create a 'Config' with the given defaults and source creators.
--   The sources will take precedence by the order they have in the list (earlier in
--   the list means it's tried first).
--   If the requested key is not found in any source it'll be looked up in the defaults.
mkConfig' :: Defaults -> [SourceCreator] -> IO Config
mkConfig' :: Defaults -> [SourceCreator] -> IO Config
mkConfig' Defaults
defaults [SourceCreator]
sources = [SourceCreator] -> Config -> IO Config
addSources [SourceCreator]
sources (Config -> IO Config) -> (Config -> Config) -> Config -> IO Config
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Defaults -> Config -> Config
addDefaults Defaults
defaults (Config -> IO Config) -> Config -> IO Config
forall a b. (a -> b) -> a -> b
$ Config
emptyConfig