packages feed

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