module LaunchDarkly.Server.Integrations.TestData.FlagBuilder
( FlagBuilder(..)
, UserKey
, VariationIndex
, newFlagBuilder
, booleanFlag
, on
, fallthroughVariation
, offVariation
, variationForAllUsers
, valueForAllUsers
, variationForUser
, variations
, buildFlag
, UserAttribute
, ifMatch
, ifNotMatch
, FlagRuleBuilder
, andMatch
, andNotMatch
, thenReturn
, Variation
)
where
import qualified Data.Aeson as Aeson
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Natural (Natural)
import qualified LaunchDarkly.Server.Features as F
import qualified LaunchDarkly.Server.Operators as Op
import Data.Function ((&))
type UserKey = Text
type VariationIndex = Integer
trueVariationForBoolean, falseVariationForBoolean :: VariationIndex
trueVariationForBoolean = 0
falseVariationForBoolean = 1
variationForBoolean :: Bool -> VariationIndex
variationForBoolean True = trueVariationForBoolean
variationForBoolean False = falseVariationForBoolean
data FlagBuilder = FlagBuilder
{ fbKey :: Text
, fbOffVariation :: Maybe VariationIndex
, fbOn :: Bool
, fbFallthroughVariation :: Maybe VariationIndex
, fbVariations :: [Aeson.Value]
, fbTargetMap :: Map UserKey VariationIndex
, fbRules :: [FlagRule]
} deriving (Show)
fbTargets :: FlagBuilder -> [F.Target]
fbTargets flagBuilder =
Map.elems $
Map.mapWithKey (flip F.Target) $
Map.foldrWithKey go mempty (fbTargetMap flagBuilder)
where
go userKey variation =
Map.insertWith (<>) variation [userKey]
buildFlag :: Natural -> FlagBuilder -> F.Flag
buildFlag version flagBuilder =
F.Flag
{ F.key = fbKey flagBuilder
, F.version = version
, F.on = fbOn flagBuilder
, F.trackEvents = False
, F.trackEventsFallthrough = False
, F.deleted = False
, F.prerequisites = []
, F.salt = "salt"
, F.targets = fbTargets flagBuilder
, F.rules = mapWithIndex convertFlagRule (fbRules flagBuilder)
, F.fallthrough = F.VariationOrRollout (fbFallthroughVariation flagBuilder) Nothing
, F.offVariation = fbOffVariation flagBuilder
, F.variations = fbVariations flagBuilder
, F.debugEventsUntilDate = Nothing
, F.clientSideAvailability = F.ClientSideAvailability False False False
}
mapWithIndex :: Integral num => (num -> a -> b) -> [a] -> [b]
mapWithIndex f l =
fmap (uncurry f) (zip [0..] l)
newFlagBuilder :: Text -> FlagBuilder
newFlagBuilder key =
FlagBuilder
{ fbKey = key
, fbOffVariation = Nothing
, fbOn = True
, fbFallthroughVariation = Nothing
, fbVariations = mempty
, fbTargetMap = mempty
, fbRules = mempty
}
booleanFlagVariations :: [Aeson.Value]
booleanFlagVariations = [Aeson.Bool True, Aeson.Bool False]
isBooleanFlag :: FlagBuilder -> Bool
isBooleanFlag flagBuilder
| booleanFlagVariations == fbVariations flagBuilder = True
| otherwise = False
booleanFlag :: FlagBuilder -> FlagBuilder
booleanFlag flagBuilder
| isBooleanFlag flagBuilder =
flagBuilder
| otherwise =
flagBuilder
& variations booleanFlagVariations
& fallthroughVariation trueVariationForBoolean
& offVariation falseVariationForBoolean
on :: Bool
-> FlagBuilder
-> FlagBuilder
on isOn fb =
fb{ fbOn = isOn }
clearRules :: FlagBuilder -> FlagBuilder
clearRules fb =
fb{ fbRules = mempty }
clearUserTargets :: FlagBuilder -> FlagBuilder
clearUserTargets fb =
fb{ fbTargetMap = mempty }
valueForAllUsers :: Aeson.ToJSON value
=> value
-> FlagBuilder
-> FlagBuilder
valueForAllUsers val fb =
fb & variations [Aeson.toJSON val]
& variationForAllUsers (0 :: VariationIndex)
variations :: [Aeson.Value]
-> FlagBuilder
-> FlagBuilder
variations values fb =
fb{ fbVariations = values }
class Variation val where
fallthroughVariation :: val
-> FlagBuilder
-> FlagBuilder
offVariation :: val
-> FlagBuilder
-> FlagBuilder
variationForAllUsers :: val
-> FlagBuilder
-> FlagBuilder
variationForUser :: UserKey
-> val
-> FlagBuilder
-> FlagBuilder
thenReturn :: val
-> FlagRuleBuilder
-> FlagBuilder
instance Variation Integer where
fallthroughVariation variationIndex fb =
fb{ fbFallthroughVariation = Just variationIndex }
offVariation variationIndex fb =
fb{ fbOffVariation = Just variationIndex }
variationForAllUsers variationIndex fb =
fb & on True
& clearRules
& clearUserTargets
& fallthroughVariation variationIndex
variationForUser userKey variationIndex fb =
fb{ fbTargetMap = Map.insert userKey variationIndex (fbTargetMap fb) }
thenReturn variationIndex ruleBuilder =
let fb = frbBaseBuilder ruleBuilder
in fb{ fbRules = FlagRule (frbClauses ruleBuilder) variationIndex : fbRules fb }
instance Variation Bool where
fallthroughVariation value fb =
fb & booleanFlag
& fallthroughVariation (variationForBoolean value)
offVariation value fb =
fb & booleanFlag
& offVariation (variationForBoolean value)
variationForAllUsers value fb =
fb & booleanFlag
& variationForAllUsers (variationForBoolean value)
variationForUser userKey value fb =
fb & booleanFlag
& variationForUser userKey (variationForBoolean value)
thenReturn value ruleBuilder =
ruleBuilder { frbBaseBuilder = booleanFlag $ frbBaseBuilder ruleBuilder }
& thenReturn (variationForBoolean value)
type UserAttribute = Text
ifMatch :: UserAttribute
-> [Aeson.Value]
-> FlagBuilder
-> FlagRuleBuilder
ifMatch userAttribute values fb =
newFlagRuleBuilder fb
& andMatch userAttribute values
ifNotMatch :: UserAttribute
-> [Aeson.Value]
-> FlagBuilder
-> FlagRuleBuilder
ifNotMatch userAttribute values fb =
newFlagRuleBuilder fb
& andNotMatch userAttribute values
data Clause = Clause
{ clauseAttribute :: UserAttribute
, clauseValues :: [Aeson.Value]
, clauseNegate :: Bool
} deriving (Show)
data FlagRule = FlagRule
{ frClauses :: [Clause]
, frVariation :: VariationIndex
} deriving (Show)
convertFlagRule :: Integer -> FlagRule -> F.Rule
convertFlagRule idx flagRule =
F.Rule
{ F.id = T.pack $ "rule" <> show idx
, F.variationOrRollout = F.VariationOrRollout (Just $ frVariation flagRule) Nothing
, F.clauses = fmap convertClause (frClauses flagRule)
, F.trackEvents = False
}
convertClause :: Clause -> F.Clause
convertClause clause =
F.Clause
{ F.attribute = clauseAttribute clause
, F.negate = clauseNegate clause
, F.values = clauseValues clause
, F.op = Op.OpIn
}
data FlagRuleBuilder = FlagRuleBuilder
{ frbClauses :: [Clause]
, frbBaseBuilder :: FlagBuilder
} deriving (Show)
newFlagRuleBuilder :: FlagBuilder -> FlagRuleBuilder
newFlagRuleBuilder baseBuilder =
FlagRuleBuilder
{ frbClauses = mempty
, frbBaseBuilder = baseBuilder
}
andMatch :: UserAttribute
-> [Aeson.Value]
-> FlagRuleBuilder
-> FlagRuleBuilder
andMatch userAttribute values ruleBuilder =
ruleBuilder{ frbClauses = Clause userAttribute values False : frbClauses ruleBuilder }
andNotMatch :: UserAttribute
-> [Aeson.Value]
-> FlagRuleBuilder
-> FlagRuleBuilder
andNotMatch userAttribute values ruleBuilder =
ruleBuilder{ frbClauses = Clause userAttribute values True : frbClauses ruleBuilder }