packages feed

conferer 0.1.0.0 → 0.1.0.1

raw patch · 11 files changed

+290/−37 lines, 11 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Conferer.Types: type Prefix = Text
+ Conferer: (/.) :: Key -> Key -> Key
+ Conferer: Provider :: (Key -> IO (Maybe Text)) -> Provider
+ Conferer: [getKeyInProvider] :: Provider -> Key -> IO (Maybe Text)
+ Conferer: addProvider :: ProviderCreator -> Config -> IO Config
+ Conferer: class FetchFromConfig a
+ Conferer: data Config
+ Conferer: data Provider
+ Conferer: emptyConfig :: Config
+ Conferer: getFromConfig :: FetchFromConfig a => Key -> Config -> IO a
+ Conferer: getKey :: Key -> Config -> IO (Either Text Text)
+ Conferer: type ProviderCreator = Config -> IO Provider
+ Conferer: unsafeGetKey :: Key -> Config -> IO Text
+ Conferer.Provider.Env: type Prefix = Text
- Conferer.Provider.Env: type LookupEnvFunc = (String -> IO (Maybe String))
+ Conferer.Provider.Env: type LookupEnvFunc = String -> IO (Maybe String)

Files

+ README.md view
@@ -0,0 +1,59 @@+# Conferer++## what, why and a bit of how++VERY UNSTABLE DON'T USE IN PRODUCTION JUST YET++Conferer is a library that defines ways to get configuration for your Haskell+application and the libraries it uses, which is at heart string keys which map+to other strings.++To get this map we use `Providers` which define a way to get a key (eg.+`db.username`) that may or may not exist, we then use a list of providers for+getting the value for that key (position on the list defines priority). This+allows adding new providers easily (for example a dhall file provider,+a git repo or a etcd database)++The other side of this is that we have the `FromConfig` which gets some value+from a `Config` at a certain key possibly using only keys under some namespacing+key++## Example (not implemented)++Let's say I want to configure warp. Let's say we wrote this program.++```haskell+main = do+  -- by default gets cli parameters, envvars and json file+  config <- getDefaultConfigFor "awesomeapp"+  warpConfig :: Warp.Settings <- getKey "warp" config++  Warp.runSettings warpConfig myApp+```++Now I need to chage the port of the app, I can change it by either:++* Setting cli params like `./myApp -Cwarp.port=5555`+* Setting an environment variable called `AWESOMEAPP_WARP_PORT=5555`+* In a json file you can have `{"warp": {"port": 5555}}`++And you may also get that value from different configuration providers like+redis or etc or whichever you may need.++## Utilities++There are as well some utilities to change providers:++* `Conferer.Provider.Namespace`: All keys must be namespaced and the namespace+  is striped for lookup+* `Conferer.Provider.Mapped`: Using a map key to maybe key you can change the+  name of a key or even hiding some key+* `Conferer.Provider.Simple`: Get keys from a hardcoded map key to string++## Future maybe things++* Interpolate keys with other keys: `{a: "db", b: "${a}_thing"}`, getting `b`+  will give `"db_thing"` (maybe)+* A LOT of providers+* A LOT of `FromConfig` implementations+* Docs
conferer.cabal view
@@ -1,14 +1,14 @@-cabal-version: 1.12+cabal-version: 1.18  -- This file has been generated from package.yaml by hpack version 0.31.2. -- -- see: https://github.com/sol/hpack ----- hash: eea931123405e74a86b3e1283519e3369618e126404652c3818e55ad1071dad5+-- hash: a139753b9c1bc2266c7b90feb57d35995b1b57ffa23dcf6f7d8a2b8dad4a7238  name:           conferer-version:        0.1.0.0-synopsis:       Configuration management library +version:        0.1.0.1+synopsis:       Configuration management library  description:    Library to abstract the parsing of many haskell config values from different config sources category:       Configuration@@ -19,6 +19,9 @@ license:        BSD3 license-file:   LICENSE build-type:     Simple+extra-doc-files:+    README.md+    LICENSE  library   exposed-modules:
src/Conferer.hs view
@@ -1,21 +1,94 @@+-- |+-- Module:      Conferer+-- Copyright:   (c) 2019 Lucas David Traverso+-- License:     MIT+-- Maintainer:  Lucas David Traverso <lucas6246@gmail.com>+-- Stability:   experimental+-- Portability: portable+--+-- Types and functions for managing configuration effectively module Conferer-  ( module Conferer.Core+  (++  -- * How to use this library+  -- | This is the most basic example: which uses the default configuration+  --   to get a configuration for warp, which can be overriden via env vars,+  --   command line arguments of @.properties@ files+  --+  -- @+  -- > import Conferer+  -- > import Conferer.FetchFromConfig.Warp () -- from package conferer-warp+  -- >+  -- > main = do+  -- >   config <- 'defaultConfig' \"awesomeapp\"+  -- >   warpSettings <- 'getFromConfig' \"warp\" config+  -- >   runSettings warpSettings application+  -- @+  --+  -- In the above example we see that we are getting a configuration value for+  -- warp under the key warp, so for example to override it's default value+  -- provided by warp the config keys for warp will always look like+  -- @warp.something@, for example to override the port for warp (3000 by+  -- default) we could call our program as @./my_program --warp.port=8000@.+  --+  -- There are two sides to conferer: Getting configuration for other libraries+  -- like warp, hspec, snap, etc. and the way we choose to provide values like+  -- json files, properties files, env vars, etc.++  -- ** Getting configuration for existing libraries+  -- | There is a typeclass 'FetchFromConfig' that defines how to get a type+  --   from a config, they are implemented in different packages since the+  --   weight of the dependencies would be too high, the package is usually+  --   named as @conferer-DEPENDENCY@ where DEPENDENCY is the name of the dependency (+  --   for example: conferer-snap, conferer-warp, conferer-hspec), if you find+  --   a library without a conferer port for its config you can create an issue+  --   or maybe even create the library yourself!++  -- ** Providing key value pairs for configuration+  -- | There is one important type in conferer: 'Config' from which, given a key+  -- (eg: @warp@) you can get anything that implements 'FetchFromConfig' (like+  -- 'Warp.Settings')+  --+  -- Internally a 'Config' is made of many 'Provider's which have a simpler+  -- interface:+  --+  -- @+  -- 'getKeyInProvider' :: Provider -> Key -> IO (Maybe Text)+  -- @+  --+  -- Most configuration providers can be abstracted away as Map String String,+  -- and they can use whatever logic they want to turn conferer keys (a list of+  -- strings) into a place to look for a string, (for example the env provider+  -- requires a string to namespace the env vars that can affect the+  -- configuration)+  --+  -- Once you have your 'Provider' you can add it to a 'Config' using the+  -- 'addProvider' function. One final note: each provider has a different+  -- priority, which depends on when is was added to the config ('Provider's+  -- added later have lower priority) so the config searches keys in providers+  -- in the same order they were added.++  module Conferer.Types+  , module Conferer.Core+  , defaultConfig+  , Key(..)++  -- * Providers   , module Conferer.Provider.Env   , module Conferer.Provider.Simple   , module Conferer.Provider.Namespaced   , module Conferer.Provider.Mapping   , module Conferer.Provider.CLIArgs   , module Conferer.Provider.Null-  , defaultConfig-  , Key(..)+  -- * Re-Exports   , (&)   ) where  import           Data.Text (Text) import           Data.Function ((&)) -import           Conferer.Core-import           Conferer.Types+import           Conferer.Core (emptyConfig, addProvider, getFromConfig, getKey, unsafeGetKey, (/.))+import           Conferer.Types (Config, Key(..), ProviderCreator, Provider(..), FetchFromConfig) import           Conferer.Provider.Env import           Conferer.Provider.Simple import           Conferer.Provider.Namespaced@@ -25,6 +98,8 @@   +-- | Default config which reads from command line arguments, env vars and+--   property files defaultConfig :: Text -> IO Config defaultConfig appName = do   pure emptyConfig
src/Conferer/Core.hs view
@@ -6,14 +6,9 @@  import           Conferer.Types -unsafeGetKey :: Key -> Config -> IO Text-unsafeGetKey k config =-  either (error . Text.unpack) id <$> getKey k config--getFromConfig :: FetchFromConfig a => Key -> Config -> IO a-getFromConfig k config =-  either (error . Text.unpack) id <$> fetch k config-+-- | Most Basic function to interact directly with a 'Config'. It always returns+--   'Text' in the case of success and implements the logic to traverse+--   providers inside the 'Config'. getKey :: Key -> Config -> IO (Either Text Text) getKey k config =   go $ providers config@@ -25,17 +20,32 @@         Just t -> return $ Right t         Nothing -> go providers ++-- | Fetch a value from a config key that's parsed using the FetchFromConfig+--   instance.+--+--   This function throws an exception if the key is not found.+getFromConfig :: FetchFromConfig a => Key -> Config -> IO a+getFromConfig k config =+  either (error . Text.unpack) id <$> fetch k config++-- | Create a new 'Key' by concatenating two existing keys. (/.) :: Key -> Key -> Key parent /. child = Path (unKey parent ++ unKey child) +-- | The empty configuration, this 'Config' is used as the base for+--   most config creating functions. emptyConfig :: Config emptyConfig = Config [] +-- | Instantiate a 'ProviderCreator' using the 'emptyConfig' mkStandaloneProvider :: ProviderCreator -> IO Provider mkStandaloneProvider mkProvider =   mkProvider emptyConfig  +-- | Instantiate a 'Provider' using an 'ProviderCretor' and a 'Config' and add+--   to the config addProvider :: ProviderCreator -> Config -> IO Config addProvider mkProvider config = do   newProvider <- mkProvider config@@ -43,3 +53,8 @@     Config     { providers = providers config ++ [ newProvider ]     }++-- | Same as 'getKey' but it throws if the 'Key' isn't found+unsafeGetKey :: Key -> Config -> IO Text+unsafeGetKey k config =+  either (error . Text.unpack) id <$> getKey k config
src/Conferer/Provider/CLIArgs.hs view
@@ -1,4 +1,14 @@-module Conferer.Provider.CLIArgs where+module Conferer.Provider.CLIArgs+  (+    -- * Command line arguments Provider+    -- | This provider provides keys from the command line arguments passed into+    -- the program. It only accepts arguments with @--@ and an equals, for+    -- example: @./awesomeapp --warp.port=5000@+    mkCLIArgsProvider+    , mkCLIArgsProvider'+    , parseArgsIntoKeyValue+  )+where  import           Data.Text (Text) import qualified Data.Text as Text@@ -10,16 +20,20 @@ import Conferer.Provider.Simple  +-- | Create a 'ProviderCreator' for CLIArgs from a argument list mkCLIArgsProvider' :: [String] -> ProviderCreator mkCLIArgsProvider' args = \config -> do   let configMap = parseArgsIntoKeyValue args   mkMapProvider configMap config +-- | Same as 'mkCLIArgsProvider'' but using 'getArgs' to provide the argument+-- list mkCLIArgsProvider :: ProviderCreator mkCLIArgsProvider = \config -> do   args <- getArgs   mkCLIArgsProvider' args config +-- | Parse an argument list into a dictionary suitable for a 'Provider' parseArgsIntoKeyValue :: [String] -> [(Key, Text)] parseArgsIntoKeyValue =   fmap (\(k, s) -> (fromString $ Text.unpack k, s)) .
src/Conferer/Provider/Env.hs view
@@ -1,27 +1,42 @@-module Conferer.Provider.Env where+module Conferer.Provider.Env+  (+-- * Env Provider+-- | This 'Provider' provides config values from env vars given a prefix that's+-- used to avoid colliding with different system configuration+--+-- For example if you use the 'Prefix' "awesomeapp" and get the 'Key'+-- "warp.port" this provider will try to lookup the env var called+-- @AWESOMEAPP_WARP_PORT@. +-- * Usage+-- | To use this provider simply choose a prefix and add it using the+-- 'addProvider' function like:+--+-- @+-- config & 'addProvider' ('mkEnvProvider' "awesomeapp")+-- @+    mkEnvProvider+    , mkEnvProvider'+    , Prefix+    , LookupEnvFunc+    , keyToEnvVar+  ) where++ import           Data.Text (Text) import qualified Data.Text as Text import qualified System.Environment as System  import           Conferer.Types --type LookupEnvFunc = (String -> IO (Maybe String))---keyToEnvVar :: Prefix -> Key -> Text-keyToEnvVar prefix (Path keys) =-  Text.toUpper-  $ Text.intercalate "_"-  $ filter (/= mempty)-  $ prefix : keys--+-- | 'ProviderCreator' for env 'Provider' that uses the real 'System.lookupEnv'+-- function mkEnvProvider :: Prefix -> ProviderCreator mkEnvProvider prefix =   mkEnvProvider' System.lookupEnv prefix +-- | 'ProviderCreator' for env 'Provider' that allows parameterizing the+-- function used to lookup for testing mkEnvProvider' :: LookupEnvFunc -> Prefix -> ProviderCreator mkEnvProvider' lookupEnv prefix = \_config ->   return $@@ -30,3 +45,23 @@       let envVarName = Text.unpack $ keyToEnvVar prefix k       fmap Text.pack <$> lookupEnv envVarName   }++-- | Type alias for the function to lookup env vars+type LookupEnvFunc = String -> IO (Maybe String)+++-- | A text to namespace env vars+type Prefix = Text++-- | Get the env name from a prefix and a key by uppercasing and+-- intercalating underscores+--+-- >>> keyToEnVar "awesomeapp" "warp.port"+-- "AWESOMEAPP_WARP_PORT"+keyToEnvVar :: Prefix -> Key -> Text+keyToEnvVar prefix (Path keys) =+  Text.toUpper+  $ Text.intercalate "_"+  $ filter (/= mempty)+  $ prefix : keys+
src/Conferer/Provider/Mapping.hs view
@@ -1,10 +1,21 @@-module Conferer.Provider.Mapping where+module Conferer.Provider.Mapping+  (+    -- * Namespaced higher-order provider+    -- | This provider takes a provider and returns a new provider that+    -- always transforms the key according to either a function for+    -- 'mkMappingProvider'' or a 'Map' for 'mkMappingProvider'+    mkMappingProvider+    , mkMappingProvider'+  )+where  import           Data.Map (Map) import qualified Data.Map as Map  import           Conferer.Types +-- | Create a 'ProviderCreator' using a function to transform the supplied keys+-- and another 'ProviderCreator' mkMappingProvider' :: (Key -> Maybe Key) -> ProviderCreator -> ProviderCreator mkMappingProvider' mapper providerCreator config = do   configProvider <- providerCreator config@@ -16,6 +27,8 @@           Nothing -> return Nothing     } +-- | Create a 'ProviderCreator' using a 'Map' 'Key' 'Key' to transform the supplied keys+-- and another 'ProviderCreator' mkMappingProvider :: Map Key Key -> ProviderCreator -> ProviderCreator mkMappingProvider configMap configProvider =   mkMappingProvider' (`Map.lookup` configMap) configProvider
src/Conferer/Provider/Namespaced.hs view
@@ -1,9 +1,17 @@-module Conferer.Provider.Namespaced where+module Conferer.Provider.Namespaced+  (+    -- * Namespaced higher-order provider+    -- | This provider takes a provider and returns a new provider that+    -- always checks that the 'Key' given always starts with certain 'Key'+    -- and then strips that prefix before consulting its inner Provider+    mkNamespacedProvider+  ) where  import           Data.List (stripPrefix)  import           Conferer.Types +-- | Create a 'ProviderCreator' from a prefix and another 'ProviderCreator' mkNamespacedProvider :: Key -> ProviderCreator -> ProviderCreator mkNamespacedProvider (Path key) configCreator = \config -> do   configProvider <- configCreator config
src/Conferer/Provider/Null.hs view
@@ -1,7 +1,14 @@-module Conferer.Provider.Null where+module Conferer.Provider.Null+  (+    -- * Does Nothing Provider+    -- | The stub provider that never has a key+    mkNullProvider+  )+where  import           Conferer.Types +-- | Create a null 'Provider' mkNullProvider :: ProviderCreator mkNullProvider _config =   return $ Provider
src/Conferer/Provider/Simple.hs view
@@ -1,4 +1,12 @@-module Conferer.Provider.Simple where+module Conferer.Provider.Simple+  (+    -- * Simple Provider+    -- | This provider provides values from a hardcoded Map passed at creation+    -- time that can not be changed afterwards, it's mostly used as a necessary+    -- utility+    mkMapProvider+  , mkMapProvider'+  ) where  import           Data.Map (Map) import qualified Data.Map as Map@@ -6,6 +14,7 @@  import           Conferer.Types +-- | Make a 'Provider' from a 'Map' mkMapProvider' :: Map Key Text -> ProviderCreator mkMapProvider' configMap _config =   return $ Provider@@ -14,5 +23,6 @@         return $ Map.lookup k configMap     } +-- | Make a 'Provider' from 'List' of 'Key', 'Text' pairs mkMapProvider :: [(Key, Text)] -> ProviderCreator mkMapProvider = mkMapProvider' . Map.fromList
src/Conferer/Types.hs view
@@ -5,29 +5,43 @@ import           Data.Text (Text) import qualified Data.Text as Text +-- | Core interface for library provided configuration, basically consists of+--   getting a 'Key' and informing returning a maybe signaling the value and+--   if it's present in that specific provider data Provider =   Provider   { getKeyInProvider :: Key -> IO (Maybe Text)   } +-- | The way to index 'Provider's, basically list of names that will be adapted+--   to whatever the provider needs newtype Key   = Path { unKey :: [Text] }   deriving (Show, Eq, Ord) +-- | Collapse a key into a textual representation keyName :: Key -> Text keyName = Text.intercalate "." . unKey +-- | Core type that the user of this library interact with, in the future it may+--   contain more this besides a list of providers data Config =   Config   { providers :: [Provider]   } +-- | The type for creating a provider given a 'Config', some providers require a+-- certain configuration to be initialized (for example: the redis provider+-- needs connection info to connect to the server) type ProviderCreator = Config -> IO Provider +-- | Main typeclass for defining the way to get values from config, hiding the+-- 'Text' based nature of the 'Provider's+--+-- Here an error means that the value couldn't be parsed and that a reasonable+-- default was not possible. class FetchFromConfig a where   fetch :: Key -> Config -> IO (Either Text a)  instance IsString Key where   fromString s = Path $ filter (/= mempty) $ Text.split (== '.') $ fromString s--type Prefix = Text