diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..616c6c0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.swift] +indent_style = space +indent_size = 4 diff --git a/Sources/Statsig/InternalStore.swift b/Sources/Statsig/InternalStore.swift index 5cf0445..4a81e13 100644 --- a/Sources/Statsig/InternalStore.swift +++ b/Sources/Statsig/InternalStore.swift @@ -7,10 +7,12 @@ fileprivate let MaxCachedUsers = 10 public struct StatsigOverrides { public var gates: [String: Bool] public var configs: [String: [String: Any]] + public var params: [String: [String: Any]] init(_ overrides: [String: Any]) { gates = overrides[InternalStore.gatesKey] as? [String: Bool] ?? [:] configs = overrides[InternalStore.configsKey] as? [String: [String: Any]] ?? [:] + params = overrides[InternalStore.paramStoresKey] as? [String: [String: Any]] ?? [:] } } @@ -486,6 +488,18 @@ class InternalStore { func getParamStore(client: StatsigClient?, forName storeName: String) -> ParameterStore { storeQueue.sync { + if let override = (localOverrides[InternalStore.paramStoresKey] as? [String: [String: StatsigDynamicConfigValue]])?[storeName] { + return ParameterStore( + name: storeName, + evaluationDetails: cache.getEvaluationDetails(.LocalOverride), + client: client, + configuration: override.mapValues {[ + "ref_type": "static", + "param_type": getTypeOf($0), + "value": $0 + ]} + ) + } return cache.getParamStore(client, storeName) } } @@ -563,11 +577,19 @@ class InternalStore { } } + func overrideParamStore(_ storeName: String, _ value: [String: Any]) { + storeQueue.async(flags: .barrier) { [weak self] in + self?.localOverrides[jsonDict: InternalStore.paramStoresKey]?[storeName] = value + self?.saveOverrides() + } + } + func removeOverride(_ name: String) { storeQueue.async(flags: .barrier) { [weak self] in self?.localOverrides[jsonDict: InternalStore.gatesKey]?.removeValue(forKey: name) self?.localOverrides[jsonDict: InternalStore.configsKey]?.removeValue(forKey: name) self?.localOverrides[jsonDict: InternalStore.layerConfigsKey]?.removeValue(forKey: name) + self?.localOverrides[jsonDict: InternalStore.paramStoresKey]?.removeValue(forKey: name) } } @@ -590,7 +612,12 @@ class InternalStore { } private static func getEmptyOverrides() -> [String: Any] { - return [InternalStore.gatesKey: [:], InternalStore.configsKey: [:], InternalStore.layerConfigsKey: [:]] + return [ + InternalStore.gatesKey: [:], + InternalStore.configsKey: [:], + InternalStore.layerConfigsKey: [:], + InternalStore.paramStoresKey: [:] + ] } // Sticky Logic: https://gist.github.com/daniel-statsig/3d8dfc9bdee531cffc96901c1a06a402 diff --git a/Sources/Statsig/Statsig.swift b/Sources/Statsig/Statsig.swift index da98978..2e7052f 100644 --- a/Sources/Statsig/Statsig.swift +++ b/Sources/Statsig/Statsig.swift @@ -467,6 +467,17 @@ public class Statsig { client?.overrideLayer(layerName, value: value) } + /** + Sets a value to be returned for the given parameter store instead of following the actual ref in the store. + + Parameters: + - storeName: The name of the config or experiment to be overridden + - value: Dictionary where keys are property names and values are static ref values in the overridden store + */ + public static func overrideParamStore(_ storeName: String, value: [String: Any]) { + client?.overrideParamStore(storeName, value: value) + } + /** Clears any overridden value for the given gate/dynamic config/experiment. diff --git a/Sources/Statsig/StatsigClient.swift b/Sources/Statsig/StatsigClient.swift index f64593f..5893093 100644 --- a/Sources/Statsig/StatsigClient.swift +++ b/Sources/Statsig/StatsigClient.swift @@ -673,6 +673,17 @@ extension StatsigClient { store.overrideLayer(layerName, value) } + /** + Sets a value to be returned for the given parameter store instead of following the actual ref in the store. + + Parameters: + - storeName: The name of the config or experiment to be overridden + - value: Dictionary where keys are property names and values are static ref values in the overridden store + */ + public func overrideParamStore(_ storeName: String, value: [String: Any]) { + store.overrideParamStore(storeName, value) + } + /** Clears any overridden value for the given gate/dynamic config/experiment. diff --git a/Tests/StatsigTests/LocalOverridesSpec.swift b/Tests/StatsigTests/LocalOverridesSpec.swift index a16882a..974447d 100644 --- a/Tests/StatsigTests/LocalOverridesSpec.swift +++ b/Tests/StatsigTests/LocalOverridesSpec.swift @@ -74,6 +74,20 @@ class LocalOverridesSpec: BaseSpec { } } + describe("parameter store overrides") { + it("returns overridden parameter store values") { + Statsig.overrideParamStore("overridden_param_store", value: ["key": "value"]) + let store = Statsig.getParameterStore("overridden_param_store") + expect(store.getValue(forKey: "key", defaultValue: "default")).to(equal("value")) + } + + it("clears overridden parameter store values") { + Statsig.overrideParamStore("overridden_param_store", value: ["key": "value"]) + Statsig.removeOverride("overridden_param_store") + expect(Statsig.getParameterStore("overridden_param_store").getValue(forKey: "key", defaultValue: "default")).to(equal("default")) + } + } + describe("clearing all overrides") { it("clears all") { Statsig.overrideGate("overridden_gate", value: true)