feature-flipper 0.1.0.0 → 0.2.0.0
raw patch · 6 files changed
+146/−69 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Control.Flipper.Types: instance GHC.Base.Monad m => Control.Flipper.Types.HasFeatureFlags (Control.Monad.Trans.State.Lazy.StateT Control.Flipper.Types.Features m)
- Control.Flipper.Types: instance GHC.Base.Monad m => Control.Flipper.Types.ModifiesFeatureFlags (Control.Monad.Trans.State.Lazy.StateT Control.Flipper.Types.Features m)
+ Control.Flipper.Adapters.Memory: FlipperT :: StateT Features m a -> FlipperT m a
+ Control.Flipper.Adapters.Memory: [unFlipperT] :: FlipperT m a -> StateT Features m a
+ Control.Flipper.Adapters.Memory: evalFlipperT :: (Monad m) => Features -> FlipperT m a -> m a
+ Control.Flipper.Adapters.Memory: execFlipperT :: (Monad m) => Features -> FlipperT m a -> m Features
+ Control.Flipper.Adapters.Memory: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Control.Flipper.Adapters.Memory.FlipperT m)
+ Control.Flipper.Adapters.Memory: instance Control.Monad.Trans.Class.MonadTrans Control.Flipper.Adapters.Memory.FlipperT
+ Control.Flipper.Adapters.Memory: instance GHC.Base.Functor m => GHC.Base.Functor (Control.Flipper.Adapters.Memory.FlipperT m)
+ Control.Flipper.Adapters.Memory: instance GHC.Base.Monad m => Control.Flipper.Types.HasFeatureFlags (Control.Flipper.Adapters.Memory.FlipperT m)
+ Control.Flipper.Adapters.Memory: instance GHC.Base.Monad m => Control.Flipper.Types.ModifiesFeatureFlags (Control.Flipper.Adapters.Memory.FlipperT m)
+ Control.Flipper.Adapters.Memory: instance GHC.Base.Monad m => Control.Monad.State.Class.MonadState Control.Flipper.Types.Features (Control.Flipper.Adapters.Memory.FlipperT m)
+ Control.Flipper.Adapters.Memory: instance GHC.Base.Monad m => GHC.Base.Applicative (Control.Flipper.Adapters.Memory.FlipperT m)
+ Control.Flipper.Adapters.Memory: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Flipper.Adapters.Memory.FlipperT m)
+ Control.Flipper.Adapters.Memory: newtype FlipperT m a
+ Control.Flipper.Adapters.Memory: runFlipperT :: (Monad m) => Features -> FlipperT m a -> m (a, Features)
+ Control.Flipper.Types: getFeature :: (HasFeatureFlags m, MonadTrans t, HasFeatureFlags m1, m ~ t m1) => FeatureName -> m (Maybe Bool)
+ Control.Flipper.Types: instance (Control.Monad.IO.Class.MonadIO m, Control.Flipper.Types.HasFeatureFlags m) => Control.Flipper.Types.HasFeatureFlags (Control.Monad.Trans.Reader.ReaderT s m)
+ Control.Flipper.Types: instance (Control.Monad.IO.Class.MonadIO m, Control.Flipper.Types.HasFeatureFlags m) => Control.Flipper.Types.HasFeatureFlags (Control.Monad.Trans.State.Lazy.StateT s m)
+ Control.Flipper.Types: instance (Control.Monad.IO.Class.MonadIO m, Control.Flipper.Types.ModifiesFeatureFlags m) => Control.Flipper.Types.ModifiesFeatureFlags (Control.Monad.Trans.Reader.ReaderT s m)
+ Control.Flipper.Types: instance (Control.Monad.IO.Class.MonadIO m, Control.Flipper.Types.ModifiesFeatureFlags m) => Control.Flipper.Types.ModifiesFeatureFlags (Control.Monad.Trans.State.Lazy.StateT s m)
+ Control.Flipper.Types: update :: ModifiesFeatureFlags m => FeatureName -> (Maybe Bool -> Maybe Bool) -> m ()
+ Control.Flipper.Types: updateFeature :: (ModifiesFeatureFlags m, MonadTrans t, ModifiesFeatureFlags m1, m ~ t m1) => FeatureName -> Bool -> m ()
- Control.Flipper.Types: class Monad m => HasFeatureFlags m
+ Control.Flipper.Types: class Monad m => HasFeatureFlags m where getFeatures = lift getFeatures getFeature = lift . getFeature
- Control.Flipper.Types: class HasFeatureFlags m => ModifiesFeatureFlags m
+ Control.Flipper.Types: class HasFeatureFlags m => ModifiesFeatureFlags m where updateFeatures = lift . updateFeatures updateFeature fName isEnabled = lift $ updateFeature fName isEnabled
- Control.Flipper.Types: getFeatures :: HasFeatureFlags m => m Features
+ Control.Flipper.Types: getFeatures :: (HasFeatureFlags m, MonadTrans t, HasFeatureFlags m1, m ~ t m1) => m Features
- Control.Flipper.Types: updateFeatures :: ModifiesFeatureFlags m => Features -> m ()
+ Control.Flipper.Types: updateFeatures :: (ModifiesFeatureFlags m, MonadTrans t, ModifiesFeatureFlags m1, m ~ t m1) => Features -> m ()
Files
- examples/environment-config/Main.hs +7/−6
- feature-flipper.cabal +2/−1
- src/Control/Flipper.hs +6/−14
- src/Control/Flipper/Adapters/Memory.hs +59/−0
- src/Control/Flipper/Types.hs +42/−10
- test/Control/FlipperSpec.hs +30/−38
examples/environment-config/Main.hs view
@@ -1,18 +1,19 @@ module Main where -import Control.Monad.State-import qualified Data.Map.Strict as Map-import Data.Maybe (maybe)-import qualified System.Environment as Env+import Control.Monad.State+import qualified Data.Map.Strict as Map+import Data.Maybe (maybe)+import qualified System.Environment as Env -import Control.Flipper+import Control.Flipper+import Control.Flipper.Adapters.Memory main :: IO () main = do features <- loadFeatures -- StateT has a HasFeatureFlags instance defined- evalStateT runWithFeatureFlags features+ evalFlipperT features runWithFeatureFlags runWithFeatureFlags :: (MonadIO m, HasFeatureFlags m) => m ()
feature-flipper.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: feature-flipper-version: 0.1.0.0+version: 0.2.0.0 synopsis: A minimally obtrusive feature flag library description: A minimally obtrusive feature flag library homepage: https://github.com/toddmohney/feature-flipper#readme@@ -32,6 +32,7 @@ exposed-modules: Control.Flipper Control.Flipper.Types+ Control.Flipper.Adapters.Memory other-modules: Paths_feature_flipper build-depends:
src/Control/Flipper.hs view
@@ -11,11 +11,9 @@ , module Control.Flipper.Types ) where -import Control.Monad (void, when)-import qualified Data.Map.Strict as M+import Control.Monad (when) -import Control.Flipper.Types (FeatureName(..), Features(..),- HasFeatureFlags(..), ModifiesFeatureFlags(..), mkFeatures)+import Control.Flipper.Types {- | The 'whenEnabled' function calls the supplied function, 'm ()', when the given@@ -41,8 +39,8 @@ enabled :: HasFeatureFlags m => FeatureName -> m Bool enabled fName = do- features <- unFeatures <$> getFeatures- if M.lookup fName features == Just True+ feature <- getFeature fName+ if feature == Just True then return True else return False @@ -55,7 +53,7 @@ -} enable :: ModifiesFeatureFlags m => FeatureName -> m ()-enable fName = update fName (\_ -> Just True)+enable fName = updateFeature fName True {- | The 'disable' function deactivates a feature.@@ -66,7 +64,7 @@ -} disable :: ModifiesFeatureFlags m => FeatureName -> m ()-disable fName = update fName (\_ -> Just False)+disable fName = updateFeature fName False {- | The 'toggle' function flips the current state of a feature.@@ -82,9 +80,3 @@ flipIt' :: Maybe Bool -> Maybe Bool flipIt' (Just a) = Just (not a) flipIt' Nothing = Just True--update :: ModifiesFeatureFlags m- => FeatureName -> (Maybe Bool -> Maybe Bool) -> m ()-update fName updateFn = do- features <- unFeatures <$> getFeatures- void . updateFeatures . Features $ M.alter updateFn fName features
+ src/Control/Flipper/Adapters/Memory.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Control.Flipper.Adapters.Memory+ ( FlipperT(..)+ , evalFlipperT+ , execFlipperT+ , runFlipperT+ ) where++import Control.Monad.State+import qualified Data.Map.Strict as Map++import Control.Flipper.Types++{- |+The 'FlipperT' transformer for in-memory feature switchable computation.+-}+newtype FlipperT m a = FlipperT { unFlipperT :: StateT Features m a }+ deriving ( Functor+ , Applicative+ , Monad+ , MonadIO+ , MonadState Features+ , MonadTrans+ )++instance (Monad m) => HasFeatureFlags (FlipperT m) where+ getFeatures = get++ getFeature featureName = do+ features <- unFeatures <$> getFeatures+ return (Map.lookup featureName features)++instance (Monad m) => ModifiesFeatureFlags (FlipperT m) where+ updateFeatures = put++ updateFeature featureName True = update featureName (\_ -> Just True)+ updateFeature featureName False = update featureName (\_ -> Just False)++{- |+Evaluates a feature-switched computation, returning the final value and+discarding the final state of the feature switches.+-}+evalFlipperT :: (Monad m) => Features -> FlipperT m a -> m a+evalFlipperT features f = evalStateT (unFlipperT f) features++{- |+Executes a feature-switched computation, returning the final state of the+feature switches, discarding the final value of the computation.+-}+execFlipperT :: (Monad m) => Features -> FlipperT m a -> m Features+execFlipperT features f = execStateT (unFlipperT f) features++{- |+Runs a feature-switched computation, returning the final value and state of the+feature switches.+-}+runFlipperT :: (Monad m) => Features -> FlipperT m a -> m (a, Features)+runFlipperT features f = runStateT (unFlipperT f) features
src/Control/Flipper/Types.hs view
@@ -1,8 +1,9 @@+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-} {-|-Module : Flipper.Types+Module : Control.Flipper.Types Description : Datatype and Typeclass definitions -} module Control.Flipper.Types@@ -10,15 +11,19 @@ , FeatureName(..) , HasFeatureFlags(..) , ModifiesFeatureFlags(..)+ , update , mkFeatures ) where -import Control.Monad.State (StateT, get, put)-import Data.Map.Strict (Map)+import Control.Monad (void)+import Control.Monad.Reader+import Control.Monad.State+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map import Data.Monoid-import Data.String (IsString (..))-import Data.Text (Text)-import qualified Data.Text as T+import Data.String (IsString (..))+import Data.Text (Text)+import qualified Data.Text as T {- | The 'HasFeatureFlags' typeclass describes how to access the Features store@@ -28,9 +33,18 @@ -- | 'getFeatures' access the Features store within the current monad getFeatures :: m Features -instance (Monad m) => HasFeatureFlags (StateT Features m) where- getFeatures = get+ default getFeatures :: (MonadTrans t, HasFeatureFlags m1, m ~ t m1) => m Features+ getFeatures = lift getFeatures + -- | 'getFeature' access a single Feature within the current monad+ getFeature :: FeatureName -> m (Maybe Bool)++ default getFeature :: (MonadTrans t, HasFeatureFlags m1, m ~ t m1) => FeatureName -> m (Maybe Bool)+ getFeature = lift . getFeature++instance (MonadIO m, HasFeatureFlags m) => HasFeatureFlags (StateT s m)+instance (MonadIO m, HasFeatureFlags m) => HasFeatureFlags (ReaderT s m)+ {- | The 'ModifiesFeatureFlags' typeclass describes how to modify the Features store within the current monad.@@ -39,9 +53,18 @@ -- | 'updateFeatures' modifies the Features store within the current monad updateFeatures :: Features -> m () -instance (Monad m) => ModifiesFeatureFlags (StateT Features m) where- updateFeatures = put+ default updateFeatures :: (MonadTrans t, ModifiesFeatureFlags m1, m ~ t m1) => Features -> m ()+ updateFeatures = lift . updateFeatures + -- | 'updateFeature' modifies a single Feature within the current monad+ updateFeature :: FeatureName -> Bool -> m ()++ default updateFeature :: (MonadTrans t, ModifiesFeatureFlags m1, m ~ t m1) => FeatureName -> Bool -> m ()+ updateFeature fName isEnabled = lift $ updateFeature fName isEnabled++instance (MonadIO m, ModifiesFeatureFlags m) => ModifiesFeatureFlags (StateT s m)+instance (MonadIO m, ModifiesFeatureFlags m) => ModifiesFeatureFlags (ReaderT s m)+ {- | An abstraction representing the current state of the features store. -}@@ -66,3 +89,12 @@ -} mkFeatures :: Map FeatureName Bool -> Features mkFeatures = Features++{- |+Updates a single Feature within the current monad+-}+update :: ModifiesFeatureFlags m+ => FeatureName -> (Maybe Bool -> Maybe Bool) -> m ()+update fName updateFn = do+ features <- unFeatures <$> getFeatures+ void . updateFeatures . Features $ Map.alter updateFn fName features
test/Control/FlipperSpec.hs view
@@ -1,10 +1,10 @@ module Control.FlipperSpec (main, spec) where -import Control.Monad.State (evalState, execState)-import qualified Data.Map.Strict as M+import qualified Data.Map.Strict as M import Test.Hspec import Control.Flipper+import Control.Flipper.Adapters.Memory main :: IO () main = hspec spec@@ -13,95 +13,87 @@ spec = do describe "whenEnabled" $ do context "the feature is enabled" $- it "evaluates the given monad" $- let- featureState = mkFeatures $ M.insert "ADD_ANOTHER_FEATURE" True mempty- store = execState- (whenEnabled "ADD_ANOTHER_FEATURE" (enable "ANOTHER_FEATURE"))- featureState- in- M.lookup "ANOTHER_FEATURE" (unFeatures store) `shouldBe` Just True+ it "evaluates the given monad" $ do+ let featureState = mkFeatures $ M.insert "ADD_ANOTHER_FEATURE" True mempty+ store <- execFlipperT featureState (whenEnabled "ADD_ANOTHER_FEATURE" (enable "ANOTHER_FEATURE"))+ M.lookup "ANOTHER_FEATURE" (unFeatures store) `shouldBe` Just True context "the feature is disabled" $- it "does not evaluate the given monad" $- let- featureState = mkFeatures $ M.insert "ADD_ANOTHER_FEATURE" False mempty- store = execState- (whenEnabled "ADD_ANOTHER_FEATURE" (enable "ANOTHER_FEATURE"))- featureState- in- M.lookup "ANOTHER_FEATURE" (unFeatures store) `shouldBe` Nothing+ it "does not evaluate the given monad" $ do+ let featureState = mkFeatures $ M.insert "ADD_ANOTHER_FEATURE" False mempty+ store <- execFlipperT featureState (whenEnabled "ADD_ANOTHER_FEATURE" (enable "ANOTHER_FEATURE"))+ M.lookup "ANOTHER_FEATURE" (unFeatures store) `shouldBe` Nothing describe "enable" $ do it "enables a new feature key" $ let featureState = mempty :: Features- result = evalState (enable "NEW_FEATURE" >> enabled "NEW_FEATURE") featureState+ result = evalFlipperT featureState (enable "NEW_FEATURE" >> enabled "NEW_FEATURE") in- result `shouldBe` True+ result `shouldReturn` True it "enables a existing feature key" $ let featureState = mkFeatures $ M.insert "FEATURE" False mempty- result = evalState (enable "FEATURE" >> enabled "FEATURE") featureState+ result = evalFlipperT featureState (enable "FEATURE" >> enabled "FEATURE") in- result `shouldBe` True+ result `shouldReturn` True describe "disable" $ do it "disables a new feature key" $ let featureState = mempty :: Features- result = evalState (disable "NEW_FEATURE" >> enabled "NEW_FEATURE") featureState+ result = evalFlipperT featureState (disable "NEW_FEATURE" >> enabled "NEW_FEATURE") in- result `shouldBe` False+ result `shouldReturn` False it "disables a existing feature key" $ let featureState = mkFeatures $ M.insert "FEATURE" True mempty- result = evalState (disable "FEATURE" >> enabled "FEATURE") featureState+ result = evalFlipperT featureState (disable "FEATURE" >> enabled "FEATURE") in- result `shouldBe` False+ result `shouldReturn` False describe "toggle" $ do it "enables a new feature key" $ let featureState = mempty :: Features- result = evalState (toggle "NEW_FEATURE" >> enabled "NEW_FEATURE") featureState+ result = evalFlipperT featureState (toggle "NEW_FEATURE" >> enabled "NEW_FEATURE") in- result `shouldBe` True+ result `shouldReturn` True it "enables a disabled feature key" $ let featureState = mkFeatures $ M.insert "FEATURE" False mempty- result = evalState (toggle "FEATURE" >> enabled "FEATURE") featureState+ result = evalFlipperT featureState (toggle "FEATURE" >> enabled "FEATURE") in- result `shouldBe` True+ result `shouldReturn` True it "disables a enabled feature key" $ let featureState = mkFeatures $ M.insert "FEATURE" True mempty- result = evalState (toggle "FEATURE" >> enabled "FEATURE") featureState+ result = evalFlipperT featureState (toggle "FEATURE" >> enabled "FEATURE") in- result `shouldBe` False+ result `shouldReturn` False describe "enabled" $ do it "returns True when the feature is enabled" $ let featureState = mkFeatures $ M.insert "ENABLED_FEATURE" True mempty- result = evalState (enabled "ENABLED_FEATURE") featureState+ result = evalFlipperT featureState (enabled "ENABLED_FEATURE") in- result `shouldBe` True+ result `shouldReturn` True it "returns False when the feature is not enabled" $ let featureState = mkFeatures $ M.insert "DISABLED_FEATURE" False mempty- result = evalState (enabled "DISABLED_FEATURE") featureState+ result = evalFlipperT featureState (enabled "DISABLED_FEATURE") in- result `shouldBe` False+ result `shouldReturn` False it "returns False when the feature key is not found" $ let featureState = mempty :: Features- result = evalState (enabled "NON_EXISTANT_FEATURE") featureState+ result = evalFlipperT featureState (enabled "NON_EXISTANT_FEATURE") in- result `shouldBe` False+ result `shouldReturn` False