packages feed

cfg-0.0.2.0: src/Cfg/Deriving/Config.hs

-- |
--  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