{-# LANGUAGE OverloadedStrings #-} -- | A small utility module that provides a foundation for dynamically enabling and disabling features. module Control.FeatureFlag where import Control.Monad import Data.Text (Text) -- | A simple toggle for selectively enabling or disabling functionality. data FeatureToggle a = Enabled | Disabled deriving (Read, Show, Eq) -- | A union of different feature providers which maintains a currently active provider and facilities for changing providers. -- -- Use this when you don\'t need to disable a feature, just to replace the implementation. data FeatureProvider a = FeatureProvider { enabledProvider :: a , enabledProviderName :: Text , availableProviders :: [(Text, a)] , defaultProvider :: a } -- | Enable a feature. enable :: FeatureToggle a -> FeatureToggle a enable = const Enabled -- | Disable a feature. disable :: FeatureToggle a -> FeatureToggle a disable = const Disabled -- | Flip a toggle from enabled to disabled or vice versa. toggle :: FeatureToggle a -> FeatureToggle a toggle t = case t of Enabled -> Disabled Disabled -> Enabled -- | Switch on values depending on whether a toggle is enabled or disabled. withToggle :: FeatureToggle a -> b -- return when the toggle is enabled -> b -- return when the toggle is disabled -> b withToggle t x y = case t of Enabled -> x Disabled -> y -- | Execute an action only when the specified feature is enabled. whenEnabled :: (Functor m, Monad m) => FeatureToggle a -> m b -> m () whenEnabled t m = case t of Enabled -> void m _ -> return () -- | Execute an action only when the specified feature is disabled. whenDisabled :: (Functor m, Monad m) => FeatureToggle a -> m b -> m () whenDisabled t m = case t of Disabled -> void m _ -> return () -- | Replace the current feature provider with another provider. -- Returns Left if the default provider is used due to a failed lookup. -- Returns Right if the lookup succeeded. -- -- Use \"default\" as the lookup value if you want to explicitly load the default provider. use :: Text -> FeatureProvider a -> Either (FeatureProvider a) (FeatureProvider a) use name p = if name == "default" then Right useDefault else case lookup name $ availableProviders p of Nothing -> Left useDefault Just ep -> Right $ p { enabledProvider = ep, enabledProviderName = name } where useDefault = p { enabledProvider = defaultProvider p, enabledProviderName = "default" } -- | Apply a function that takes a feature provided by a "FeatureProvider". withProvider :: FeatureProvider a -> (a -> b) -> b withProvider p f = f $ enabledProvider p