-- | -- Module : Cfg.Deriving.Config -- Copyright : © Jonathan Lorimer, 2023 -- License : MIT -- Maintainer : jonathanlorimer@pm.me -- Stability : stable -- -- @since 0.0.2.0 -- -- This module provides types and instances for deriving 'ConfigSource' and -- 'ConfigParser' instances via generic machinery. These types are also how you -- modify the key representation for your configuration. module Cfg.Deriving.Config ( -- * Deriving Types Config (..) , ConfigOpts (..) , ConfigRoot (..) -- * Internal Typeclasses , GetConfigOptions (..) , ConfigRootOptions (..) ) where import Cfg.Deriving.Assert (AssertTopLevelRecord) import Cfg.Deriving.KeyModifier import Cfg.Options import Cfg.Parser import Cfg.Parser.Config import Cfg.Source import Cfg.Source.Config import Cfg.Source.Default import Data.Coerce import GHC.Generics -- $setup -- >>> import GHC.Generics (Generic (..)) -- >>> import Cfg.Source (ConfigSource(..)) -- >>> import Cfg.Parser (ConfigParser(..)) -- >>> import Text.Pretty.Simple -- | This newtype is the simplest deriving option. It doesn't allow you to -- alter key names with a 'Cfg.Deriving.KeyModifier.KeyModifier', it only -- specifies record fields as keys within the configuration tree hierarchy. -- Therefore it is not possible to derive this for configuration /values/ (such -- as product types without named record fields, or sum types), only top level -- records. -- -- ===== __Example__ -- -- >>> import GHC.Generics (Generic (..)) -- >>> import Cfg.Source (ConfigSource(..)) -- >>> import Cfg.Parser (ConfigParser(..)) -- >>> import Cfg.Deriving.Config (Config(..)) -- >>> import Cfg.Source.Default (DefaultSource(..)) -- >>> :{ -- data AppConfig = AppConfig -- { appConfigSetting1 :: Int -- , appConfigSetting2 :: Bool -- , appConfigSetting3 :: String -- } -- deriving (Generic, Show, DefaultSource) -- deriving (ConfigSource, ConfigParser) via (Config AppConfig) -- :} -- -- >>> pPrint $ configSource @AppConfig -- Free -- ( fromList -- [ -- ( "appConfigSetting1" -- , Free -- ( fromList [] ) -- ) -- , -- ( "appConfigSetting2" -- , Free -- ( fromList [] ) -- ) -- , -- ( "appConfigSetting3" -- , Free -- ( fromList [] ) -- ) -- ] -- ) -- -- @since 0.0.2.0 newtype Config a = Config {unConfig :: a} -- | @since 0.0.2.0 instance (Generic a) => Generic (Config a) where type Rep (Config a) = Rep a to = Config . to from (Config x) = from x -- | This newtype is identical to 'Config' except that it accepts a type -- argument which can be used to apply a 'Cfg.Deriving.KeyModifier.KeyModifier' -- to each record field name when generating keys. -- -- ===== __Example__ -- -- >>> import GHC.Generics (Generic (..)) -- >>> import Cfg.Source (ConfigSource(..)) -- >>> import Cfg.Parser (ConfigParser(..)) -- >>> import Cfg.Deriving.Config (Config(..)) -- >>> import Cfg.Source.Default (DefaultSource(..)) -- >>> :{ -- data AppConfig = AppConfig -- { appConfigSetting1 :: Int -- , appConfigSetting2 :: Bool -- , appConfigSetting3 :: String -- } -- deriving (Generic, Show, DefaultSource) -- deriving (ConfigSource, ConfigParser) -- via (ConfigOpts '[StripPrefix "app", CamelToSnake, ToUpper] AppConfig) -- :} -- -- >>> pPrint $ configSource @AppConfig -- Free -- ( fromList -- [ -- ( "CONFIG_SETTING1" -- , Free -- ( fromList [] ) -- ) -- , -- ( "CONFIG_SETTING2" -- , Free -- ( fromList [] ) -- ) -- , -- ( "CONFIG_SETTING3" -- , Free -- ( fromList [] ) -- ) -- ] -- ) -- -- @since 0.0.2.0 newtype ConfigOpts fieldModifier a = ConfigOpts {unConfigOptions :: a} -- | @since 0.0.2.0 instance (Generic a) => Generic (ConfigOpts t a) where type Rep (ConfigOpts t a) = Rep a to = ConfigOpts . to from (ConfigOpts x) = from x -- | This newtype is used to derive instances for your root configuration type -- (i.e. the top level record for all your configuration). The only additional -- functionality that it provides is that it lets you specify a root key, which -- is derived from either the type name or the data constructor name. You -- choose which name you select by providing either -- 'Cfg.Options.ConstructorName` or 'Cfg.Options.TypeName' as the first type -- argument to `ConfigRoot`. These `Cfg.Options.RootKey` types also take a type -- level argument where you can provide key modifiers, if you don't want to -- apply any key modifiers you can pass in 'Cfg.Deriving.KeyModifier.Identity' -- or an empty tuple or an empty type level list. -- -- ===== __@TypeName@ Example__ -- -- >>> import GHC.Generics (Generic (..)) -- >>> import Cfg.Source (ConfigSource(..)) -- >>> import Cfg.Parser (ConfigParser(..)) -- >>> import Cfg.Deriving.Config (Config(..)) -- >>> import Cfg.Source.Default (DefaultSource(..)) -- >>> import Cfg.Deriving.KeyModifier -- >>> :{ -- data TypeNameConfig = ConfigConstructor -- { appConfigSetting1 :: Int -- , appConfigSetting2 :: Bool -- , appConfigSetting3 :: String -- } -- deriving (Generic, Show, DefaultSource) -- deriving (ConfigSource, ConfigParser) -- via ConfigRoot -- ('TypeName '[StripSuffix "Config", CamelToSnake, ToUpper]) -- '[StripPrefix "app", CamelToSnake, ToUpper] -- TypeNameConfig -- :} -- -- >>> pPrint $ configSource @TypeNameConfig -- Free -- ( fromList -- [ -- ( "TYPE_NAME" -- , Free -- ( fromList -- [ -- ( "CONFIG_SETTING1" -- , Free -- ( fromList [] ) -- ) -- , -- ( "CONFIG_SETTING2" -- , Free -- ( fromList [] ) -- ) -- , -- ( "CONFIG_SETTING3" -- , Free -- ( fromList [] ) -- ) -- ] -- ) -- ) -- ] -- ) -- -- ===== __@ConstructorName@ Example__ -- -- >>> :{ -- data TypeNameConfig = ConfigConstructor -- { appConfigSetting1 :: Int -- , appConfigSetting2 :: Bool -- , appConfigSetting3 :: String -- } -- deriving (Generic, Show, DefaultSource) -- deriving (ConfigSource, ConfigParser) -- via ConfigRoot -- ('ConstructorName Identity) -- '[StripPrefix "app", CamelToSnake, ToUpper] -- TypeNameConfig -- :} -- -- >>> pPrint $ configSource @TypeNameConfig -- Free -- ( fromList -- [ -- ( "ConfigConstructor" -- , Free -- ( fromList -- [ -- ( "CONFIG_SETTING1" -- , Free -- ( fromList [] ) -- ) -- , -- ( "CONFIG_SETTING2" -- , Free -- ( fromList [] ) -- ) -- , -- ( "CONFIG_SETTING3" -- , Free -- ( fromList [] ) -- ) -- ] -- ) -- ) -- ] -- ) -- -- @since 0.0.2.0 newtype ConfigRoot rootType fieldModifier a = ConfigRoot {unConfigRoot :: a} -- | Typeclass for reifying type level field label modifiers into 'Cfg.Options.KeyOptions' -- -- @since 0.0.2.0 class (KeyModifier t) => GetConfigOptions t where getOptions :: KeyOptions -- | @since 0.0.2.0 instance (KeyModifier t) => GetConfigOptions t where getOptions = KeyOptions (getKeyModifier @t) -- | @since 0.0.2.0 instance (Generic a) => Generic (ConfigRoot r f a) where type Rep (ConfigRoot r f a) = Rep a to = ConfigRoot . to from (ConfigRoot x) = from x -- | Typeclass for reifying type level arguments into 'Cfg.Options.RootOptions' -- -- @since 0.0.2.0 class (KeyModifier r, KeyModifier f) => ConfigRootOptions r f where configRootOptions :: RootOptions -- | @since 0.0.2.0 instance (KeyModifier (TypeName k), KeyModifier f) => ConfigRootOptions (TypeName k) f where configRootOptions = RootOptions (TypeName $ getKeyModifier @(TypeName k)) (getKeyModifier @f) -- | @since 0.0.2.0 instance (KeyModifier (ConstructorName k), KeyModifier f) => ConfigRootOptions (ConstructorName k) f where configRootOptions = RootOptions (ConstructorName $ getKeyModifier @(ConstructorName k)) (getKeyModifier @f) -- Source -- | @since 0.0.2.0 instance (AssertTopLevelRecord ConfigSource a, DefaultSource a, Generic a, GConfigSource (Rep a)) => ConfigSource (Config a) where configSource = defaultConfigSource @a defaultConfigOptions -- | @since 0.0.2.0 instance ( GetConfigOptions t , AssertTopLevelRecord ConfigSource a , Generic a , DefaultSource a , GConfigSource (Rep a) ) => ConfigSource (ConfigOpts t a) where configSource = defaultConfigSource @a (Key $ getOptions @t) -- | @since 0.0.2.0 instance ( ConfigRootOptions r f , AssertTopLevelRecord ConfigSource a , Generic a , DefaultSource a , GConfigSource (Rep a) ) => ConfigSource (ConfigRoot r f a) where configSource = defaultConfigSource @a (Root $ configRootOptions @r @f) -- Parser -- | @since 0.0.2.0 instance (AssertTopLevelRecord ConfigParser a, Generic a, GConfigParser (Rep a)) => ConfigParser (Config a) where parseConfig keyTree = coerce `asTypeOf` fmap Config $ defaultParseConfig defaultConfigOptions keyTree -- | @since 0.0.2.0 instance ( GetConfigOptions t , AssertTopLevelRecord ConfigSource a , Generic a , GConfigParser (Rep a) ) => ConfigParser (ConfigOpts t a) where parseConfig keyTree = coerce `asTypeOf` fmap ConfigOpts $ defaultParseConfig (Key $ getOptions @t) keyTree -- | @since 0.0.2.0 instance ( ConfigRootOptions r f , AssertTopLevelRecord ConfigParser a , Generic a , GConfigParser (Rep a) ) => ConfigParser (ConfigRoot r f a) where parseConfig keyTree = coerce `asTypeOf` fmap ConfigRoot $ defaultParseConfig (Root $ configRootOptions @r @f) keyTree