settei-0.1.0.0: src/Settei/Internal/Config.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Settei.Internal.Config
( Config (..),
Default (..),
Request (..),
RuleName (..),
caseDefaultConfig,
constantDefaultConfig,
describeConfig,
derivedDefaultConfig,
optionalConfig,
renderRuleName,
requiredConfig,
runConfig,
withDefaultConfig,
)
where
import Control.Selective (Selective (..))
import Data.List.NonEmpty qualified as NonEmpty
import Settei.Internal.Schema
( Requirement (..),
Schema,
combineSchema,
conditionalSchema,
emptySchema,
requestSchema,
)
import Settei.Prelude
import Settei.Setting (Setting)
-- | A request made by the declaration language.
data Request a where
RequiredRequest :: !(Setting a) -> Request a
OptionalRequest :: !(Setting a) -> Request (Maybe a)
-- | Stable identity for a default derivation rule.
newtype RuleName = RuleName Text
deriving stock (Generic, Eq, Ord, Show)
-- | An inspectable fallback with explicit dependencies.
--
-- Function-bearing constructors intentionally have no 'Show' instance.
data Default a where
ConstantDefault :: !RuleName -> !Text -> a -> Default a
DerivedDefault :: !RuleName -> !Text -> Config d -> (d -> a) -> Default a
CaseDefault ::
(Ord d, Show d) =>
!RuleName ->
!Text ->
Config d ->
!(NonEmpty (d, a)) ->
!(Maybe a) ->
Default a
-- | The private, typed syntax tree behind the public declaration language.
data Config a where
PureConfig :: a -> Config a
MapConfig :: (a -> b) -> Config a -> Config b
ApplyConfig :: Config (a -> b) -> Config a -> Config b
RequestConfig :: !(Request a) -> Config a
DefaultConfig :: !(Setting a) -> !(Default a) -> Config a
SelectConfig :: Config (Either a b) -> Config (a -> b) -> Config b
instance Functor Config where
fmap = MapConfig
instance Applicative Config where
pure = PureConfig
(<*>) = ApplyConfig
instance Selective Config where
select = SelectConfig
requiredConfig :: Setting a -> Config a
requiredConfig = RequestConfig . RequiredRequest
optionalConfig :: Setting a -> Config (Maybe a)
optionalConfig = RequestConfig . OptionalRequest
constantDefaultConfig :: RuleName -> Text -> a -> Default a
constantDefaultConfig = ConstantDefault
derivedDefaultConfig :: RuleName -> Text -> Config d -> (d -> a) -> Default a
derivedDefaultConfig = DerivedDefault
caseDefaultConfig ::
(Ord d, Show d) =>
RuleName ->
Text ->
Config d ->
NonEmpty (d, a) ->
Maybe a ->
Default a
caseDefaultConfig = CaseDefault
withDefaultConfig :: Setting a -> Default a -> Config a
withDefaultConfig = DefaultConfig
-- | Render the stable textual identity of a default rule.
renderRuleName :: RuleName -> Text
renderRuleName (RuleName value) = value
-- | Interpret a declaration. Selective branches evaluate only the chosen side.
runConfig :: (Monad m) => (forall x. Request x -> m x) -> Config a -> m a
runConfig interpret = \case
PureConfig value -> pure value
MapConfig mapValue config -> mapValue <$> runConfig interpret config
ApplyConfig function inputConfig ->
runConfig interpret function <*> runConfig interpret inputConfig
RequestConfig request -> interpret request
DefaultConfig settingSpec defaultSpec -> do
explicitValue <- interpret (OptionalRequest settingSpec)
case explicitValue of
Just value -> pure value
Nothing -> runDefault defaultSpec
SelectConfig selector branch -> do
decision <- runConfig interpret selector
case decision of
Right value -> pure value
Left value -> do
applyBranch <- runConfig interpret branch
pure (applyBranch value)
where
runDefault = \case
ConstantDefault _ _ value -> pure value
DerivedDefault _ _ dependency derive ->
derive <$> runConfig interpret dependency
CaseDefault _ _ dependency choices fallback -> do
dependencyValue <- runConfig interpret dependency
case lookup dependencyValue (NonEmpty.toList choices) of
Just value -> pure value
Nothing -> case fallback of
Just value -> pure value
Nothing -> error "Settei.Internal.Config.runConfig: unmatched case default"
describeConfig :: Config a -> Schema
describeConfig = \case
PureConfig _ -> emptySchema
MapConfig _ config -> describeConfig config
ApplyConfig function inputConfig ->
combineSchema (describeConfig function) (describeConfig inputConfig)
RequestConfig request -> case request of
RequiredRequest settingSpec -> requestSchema Required settingSpec
OptionalRequest settingSpec -> requestSchema Optional settingSpec
DefaultConfig settingSpec defaultSpec ->
conditionalSchema
(requestSchema Optional settingSpec)
(describeDefault defaultSpec)
SelectConfig selector branch ->
conditionalSchema (describeConfig selector) (describeConfig branch)
describeDefault :: Default a -> Schema
describeDefault = \case
ConstantDefault _ _ _ -> emptySchema
DerivedDefault _ _ dependency _ -> describeConfig dependency
CaseDefault _ _ dependency _ _ -> describeConfig dependency