From 662476b9fd3ad637f8fe0e5258a3b0dedb43fa1d Mon Sep 17 00:00:00 2001 From: Beth Rennie Date: Mon, 17 Aug 2026 15:30:22 -0400 Subject: [PATCH] Bug 2053517 - Remove pref-key support This feature has not been used in a long time and leads to a lot of confusion with gecko-prefs. --- CHANGELOG.md | 8 +- .../nimbus/internal/FeatureHolder.kt | 13 +-- .../nimbus-fml/fixtures/fe/misc-features.yaml | 8 -- .../fixtures/fe/pref_overrides.fml.yaml | 35 -------- .../src/backends/frontend_manifest.rs | 1 - .../backends/kotlin/gen_structs/bundled.rs | 10 --- .../backends/kotlin/gen_structs/filters.rs | 15 ---- .../backends/kotlin/gen_structs/primitives.rs | 27 ------ .../kotlin/templates/FeatureTemplate.kt | 16 ---- .../src/backends/kotlin/templates/macros.kt | 23 ----- .../support/nimbus-fml/src/backends/mod.rs | 9 -- .../swift/templates/FeatureTemplate.swift | 23 ----- .../src/backends/swift/templates/macros.swift | 29 +------ .../support/nimbus-fml/src/fixtures/mod.rs | 12 +-- components/support/nimbus-fml/src/frontend.rs | 6 -- .../src/intermediate_representation.rs | 25 ------ .../nimbus-fml/test/pref_overrides.kts | 66 --------------- .../nimbus-fml/test/pref_overrides.swift | 83 ------------------- 18 files changed, 15 insertions(+), 394 deletions(-) delete mode 100644 components/support/nimbus-fml/fixtures/fe/pref_overrides.fml.yaml delete mode 100644 components/support/nimbus-fml/test/pref_overrides.kts delete mode 100644 components/support/nimbus-fml/test/pref_overrides.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index bba0478286e..2b488c1a78e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # v156.0 (In progress) +## ✨ What's Changed ✨ + +### Nimbus + +- The `pref-key` annotation for FML feature variables has been removed. ([#TODO](TODO)) + [Full Changelog](In progress) # v155.0 (_2026-08-13_) @@ -36,7 +42,7 @@ In the cases where the failed before, they're now no-ops. ### Remote Settings - Replacing v1 routes with v2 routes, removing added v2 routes ([#7492](https://github.com/mozilla/application-services/pull/7339)) -- Verify signature of imported data when `.get()` is called with `sync_if_empty: true` ([#7518](https://github.com/mozilla/application-services/pull/7518)) +- Verify signature of imported data when `.get()` is called with `sync_if_empty: true` ([#7518](https://github.com/mozilla/application-services/pull/7518)) - Do not quote `_since` values with the v2 API ([#7523](https://github.com/mozilla/application-services/pull/7523)) ### Sync Manager diff --git a/components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/internal/FeatureHolder.kt b/components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/internal/FeatureHolder.kt index 597ee201ffe..9e505479c5a 100644 --- a/components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/internal/FeatureHolder.kt +++ b/components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/internal/FeatureHolder.kt @@ -182,18 +182,9 @@ interface FMLObjectInterface { } /** - * A bare-bones interface for the FML generated features. + * A marker interface for FML generated feature classes. * * App developers should use the generated concrete classes, which * implement this interface. */ -interface FMLFeatureInterface : FMLObjectInterface { - /** - * A test if the feature configuration has been modified somehow, invalidating any experiment - * that uses it. - * - * This may be `true` if a `pref-key` has been set in the feature manifest and the user has - * set that preference. - */ - fun isModified(): Boolean = false -} +interface FMLFeatureInterface : FMLObjectInterface {} diff --git a/components/support/nimbus-fml/fixtures/fe/misc-features.yaml b/components/support/nimbus-fml/fixtures/fe/misc-features.yaml index 94d6bf41e41..bac43a53d8a 100644 --- a/components/support/nimbus-fml/fixtures/fe/misc-features.yaml +++ b/components/support/nimbus-fml/fixtures/fe/misc-features.yaml @@ -10,14 +10,6 @@ about: channels: - debug features: - onboarding: - description: A feature containing a field with pref-key - variables: - enabled: - description: If true, enable new style onboarding. - type: Boolean - pref-key: enrollment_enabled - default: false messaging: description: A feature allowing coenrollment allow-coenrollment: true diff --git a/components/support/nimbus-fml/fixtures/fe/pref_overrides.fml.yaml b/components/support/nimbus-fml/fixtures/fe/pref_overrides.fml.yaml deleted file mode 100644 index aabce79601f..00000000000 --- a/components/support/nimbus-fml/fixtures/fe/pref_overrides.fml.yaml +++ /dev/null @@ -1,35 +0,0 @@ ---- -about: - description: A test for gating via preference - kotlin: - class: .AppConfig - package: com.example.nimbus - swift: - class: AppConfig - module: App -channels: - - debug -features: - my-feature: - description: A feature with preference overrides - variables: - my-boolean: - type: Boolean - description: A boolean - pref-key: my-boolean-pref-key - default: false - my-int: - type: Int - description: An Int - pref-key: my-int-pref-key - default: 0 - my-string: - type: String - description: A String - pref-key: my-string-pref-key - default: from manifest - my-text: - type: Text - description: A Text - pref-key: my-text-pref-key - default: from manifest diff --git a/components/support/nimbus-fml/src/backends/frontend_manifest.rs b/components/support/nimbus-fml/src/backends/frontend_manifest.rs index fcf08f4f4e4..0fee390642b 100644 --- a/components/support/nimbus-fml/src/backends/frontend_manifest.rs +++ b/components/support/nimbus-fml/src/backends/frontend_manifest.rs @@ -142,7 +142,6 @@ impl From for FieldBody { impl From for FeatureFieldBody { fn from(value: PropDef) -> Self { Self { - pref_key: value.pref_key.clone(), gecko_pref: value.gecko_pref.clone(), string_alias: value.string_alias.as_ref().map(TypeRef::to_string), field: value.into(), diff --git a/components/support/nimbus-fml/src/backends/kotlin/gen_structs/bundled.rs b/components/support/nimbus-fml/src/backends/kotlin/gen_structs/bundled.rs index df606392127..5f66aab839c 100644 --- a/components/support/nimbus-fml/src/backends/kotlin/gen_structs/bundled.rs +++ b/components/support/nimbus-fml/src/backends/kotlin/gen_structs/bundled.rs @@ -86,16 +86,6 @@ impl CodeType for TextCodeType { } } - fn preference_getter( - &self, - oracle: &dyn CodeOracle, - prefs: &dyn Display, - pref_key: &dyn Display, - ) -> Option { - let ct = oracle.find(&TypeRef::String); - ct.preference_getter(oracle, prefs, pref_key) - } - fn is_resource_id(&self, literal: &Literal) -> bool { match literal { serde_json::Value::String(v) => is_resource_id(v), diff --git a/components/support/nimbus-fml/src/backends/kotlin/gen_structs/filters.rs b/components/support/nimbus-fml/src/backends/kotlin/gen_structs/filters.rs index 122c4b3b75a..5a7dea26cec 100644 --- a/components/support/nimbus-fml/src/backends/kotlin/gen_structs/filters.rs +++ b/components/support/nimbus-fml/src/backends/kotlin/gen_structs/filters.rs @@ -49,21 +49,6 @@ pub fn property( Ok(ct.property_getter(oracle, &vars, &prop, &default)) } -pub fn preference_getter( - type_: impl Borrow, - _: &dyn askama::Values, - prefs: impl fmt::Display, - pref_key: impl fmt::Display, -) -> Result { - let oracle = &ConcreteCodeOracle; - let ct = oracle.find(type_.borrow()); - if let Some(getter) = ct.preference_getter(oracle, &prefs, &pref_key) { - Ok(getter) - } else { - unreachable!("The preference for type {} isn't available. This is a bug in Nimbus FML Kotlin generator", type_.borrow()); - } -} - pub fn to_json( prop: impl fmt::Display, _: &dyn askama::Values, diff --git a/components/support/nimbus-fml/src/backends/kotlin/gen_structs/primitives.rs b/components/support/nimbus-fml/src/backends/kotlin/gen_structs/primitives.rs index d5811700a94..942dc685489 100644 --- a/components/support/nimbus-fml/src/backends/kotlin/gen_structs/primitives.rs +++ b/components/support/nimbus-fml/src/backends/kotlin/gen_structs/primitives.rs @@ -66,15 +66,6 @@ impl CodeType for BooleanCodeType { _ => unreachable!("Expecting a boolean"), } } - - fn preference_getter( - &self, - _oracle: &dyn CodeOracle, - prefs: &dyn Display, - pref_key: &dyn Display, - ) -> Option { - Some(format!("{prefs}.getBoolean({}, false)", quoted(pref_key))) - } } pub(crate) struct IntCodeType; @@ -131,15 +122,6 @@ impl CodeType for IntCodeType { _ => unreachable!("Expecting a number"), } } - - fn preference_getter( - &self, - _oracle: &dyn CodeOracle, - prefs: &dyn Display, - pref_key: &dyn Display, - ) -> Option { - Some(format!("{prefs}.getInt({}, 0)", quoted(pref_key))) - } } pub(crate) struct StringCodeType; @@ -199,15 +181,6 @@ impl CodeType for StringCodeType { _ => unreachable!("Expecting a string"), } } - - fn preference_getter( - &self, - _oracle: &dyn CodeOracle, - prefs: &dyn Display, - pref_key: &dyn Display, - ) -> Option { - Some(format!("{prefs}.getString({}, \"\")", quoted(pref_key))) - } } #[cfg(test)] diff --git a/components/support/nimbus-fml/src/backends/kotlin/templates/FeatureTemplate.kt b/components/support/nimbus-fml/src/backends/kotlin/templates/FeatureTemplate.kt index 4a76d7f72ef..d2f491c141e 100644 --- a/components/support/nimbus-fml/src/backends/kotlin/templates/FeatureTemplate.kt +++ b/components/support/nimbus-fml/src/backends/kotlin/templates/FeatureTemplate.kt @@ -4,20 +4,4 @@ {{ inner.doc()|comment("") }} public class {{ inner.name()|class_name }} {% call kt::render_constructor() %} : FMLFeatureInterface { {% call kt::render_class_body(inner) %} - - {%- if inner.has_prefs() %} - override fun isModified(): Boolean = - {% call kt::prefs() %}?.let { prefs -> - listOf( - {%- for p in inner.props() %} - {%- if p.has_prefs() %} - {{ p.pref_key().unwrap()|quoted }}, - {%- endif %} - {%- if p.has_gecko_prefs() %} - {{ p.gecko_pref().unwrap().pref()|quoted }}, - {%- endif %} - {%- endfor %} - ).any { prefs.contains(it) } - } ?: false - {%- endif %} } diff --git a/components/support/nimbus-fml/src/backends/kotlin/templates/macros.kt b/components/support/nimbus-fml/src/backends/kotlin/templates/macros.kt index a85ae1f9941..dedd0494438 100644 --- a/components/support/nimbus-fml/src/backends/kotlin/templates/macros.kt +++ b/components/support/nimbus-fml/src/backends/kotlin/templates/macros.kt @@ -47,29 +47,6 @@ private constructor( {%- let defaults = format!("_defaults.{}", prop_kt) %} {%- let getter = p.typ()|property(p.name(), "_variables", defaults) %} {{ p.doc()|comment(" ") }} - {%- if !p.has_prefs() %} - val {{ prop_kt }}: {{ type_kt }} by lazy { - {{ getter }} - } - {%- else %} - val {{ prop_kt }}: {{ type_kt }} - get() = - {%- let prefs = "it" %} - {%- let key = p.pref_key().unwrap() %} - {% call prefs() %}?.let { - if ({{ prefs }}.contains({{ key|quoted }})) { - try { - {{ p.typ()|preference_getter(prefs, key) }} - } catch (e: ClassCastException) { - // This only gets to here if the app has written the - // wrong type to this preference. - null - } - } else { - null - } - } ?: {{ getter }} - {%- endif %} {% endfor %} {#- toJSON #} diff --git a/components/support/nimbus-fml/src/backends/mod.rs b/components/support/nimbus-fml/src/backends/mod.rs index 6e18ac20d16..e00481e16b0 100644 --- a/components/support/nimbus-fml/src/backends/mod.rs +++ b/components/support/nimbus-fml/src/backends/mod.rs @@ -145,15 +145,6 @@ pub trait CodeType { None } - fn preference_getter( - &self, - _oracle: &dyn CodeOracle, - _prefs: &dyn Display, - _pref_key: &dyn Display, - ) -> Option { - None - } - /// Call from the template fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String { self.as_json_transform(oracle, prop) diff --git a/components/support/nimbus-fml/src/backends/swift/templates/FeatureTemplate.swift b/components/support/nimbus-fml/src/backends/swift/templates/FeatureTemplate.swift index 42873bcefcf..615348bcb4c 100644 --- a/components/support/nimbus-fml/src/backends/swift/templates/FeatureTemplate.swift +++ b/components/support/nimbus-fml/src/backends/swift/templates/FeatureTemplate.swift @@ -2,27 +2,4 @@ {%- let inner = self.inner() %} {%- let class_name = inner.name()|class_name -%} {% call swift::render_class(inner) %} - -{%- if inner.has_prefs() %} - -extension {{ class_name }}: FMLFeatureInterface { - public func isModified() -> Bool { - guard let prefs = {% call swift::prefs() %} else { - return false - } - let keys = [ - {%- for p in inner.props() %} - {%- if p.has_prefs() %} - {{ p.pref_key().unwrap()|quoted }}, - {%- endif %} - {%- endfor %} - ] - if let _ = keys.first(where: { prefs.object(forKey: $0) != nil }) { - return true - } - return false - } -} -{%- else %} extension {{ class_name }}: FMLFeatureInterface {} -{%- endif %} diff --git a/components/support/nimbus-fml/src/backends/swift/templates/macros.swift b/components/support/nimbus-fml/src/backends/swift/templates/macros.swift index 1377d45e1c1..cc7a8ed4703 100644 --- a/components/support/nimbus-fml/src/backends/swift/templates/macros.swift +++ b/components/support/nimbus-fml/src/backends/swift/templates/macros.swift @@ -13,15 +13,13 @@ {% let class_name = inner.name()|class_name -%} {{ inner.doc()|comment("") }} -public class {{class_name}}: FMLObjectInterface { +public class {{class_name}}: FMLFeatureInterface { private let _variables: Variables private let _defaults: Defaults - private let _prefs: UserDefaults? - private init(variables: Variables = NilVariables.instance, prefs: UserDefaults? = nil, defaults: Defaults) { + private init(variables: Variables = NilVariables.instance, defaults: Defaults) { self._variables = variables self._defaults = defaults - self._prefs = prefs } {# The struct holds the default values that come from the manifest. They should completely specify all values needed for the feature #} @@ -70,33 +68,10 @@ public class {{class_name}}: FMLObjectInterface { {%- let defaults = format!("_defaults.{}", prop_swift) %} {%- let getter = p.typ()|property(p.name(), "self._variables", defaults) %} {{ p.doc()|comment(" ") }} - {%- if !p.has_prefs() %} public lazy var {{ prop_swift }}: {{ type_swift }} = { {{ getter }} }() - {%- else %} - public var {{ prop_swift }}: {{ type_swift }} { - {%- let prefs = "prefs" %} - {%- let key = p.pref_key().unwrap() %} - {#- Using `object(forKey:)` here as it returns an optional that just needs to be cast. - `integer(forKey:)` returns zero, `bool(forKey:)` returns `false` if the key is not - present. - - Now we only need to use the type label as part of the cast, we don't need to implement - separate type specific preference getters. - - `has_prefs()` checks if the type can be got from UserDefaults. - #} - if let {{ prefs }} = {% call prefs() %}, - let {{ prop_swift }} = {{ prefs }}.object(forKey: {{ key|quoted }}) as? {{ type_swift }} { - return {{ prop_swift }} - } - return {{ getter }} - } - {%- endif %} {% endfor %} } {%- endmacro %}} - -{% macro prefs() %}self._prefs{% endmacro %} diff --git a/components/support/nimbus-fml/src/fixtures/mod.rs b/components/support/nimbus-fml/src/fixtures/mod.rs index 53a64f2b756..e327dd8bbef 100644 --- a/components/support/nimbus-fml/src/fixtures/mod.rs +++ b/components/support/nimbus-fml/src/fixtures/mod.rs @@ -54,7 +54,6 @@ impl PropDef { typ: typ.clone(), default: value.clone(), doc: format!("{nm} property of type {typ}"), - pref_key: None, gecko_pref: None, string_alias: None, } @@ -64,18 +63,17 @@ impl PropDef { nm: &str, typ: &TypeRef, value: &Value, - pref_key: &str, - pref_branch: PrefBranch, + pref: &str, + branch: PrefBranch ) -> Self { Self { name: nm.to_string(), typ: typ.clone(), default: value.clone(), doc: format!("{nm} property of type {typ}"), - pref_key: None, gecko_pref: Some(GeckoPrefDef { - pref: pref_key.into(), - branch: pref_branch, + pref: pref.into(), + branch: branch, }), string_alias: None, } @@ -87,7 +85,6 @@ impl PropDef { typ: typ.clone(), default: value.clone(), doc: nm.to_string(), - pref_key: None, gecko_pref: None, string_alias: Some(sa.clone()), } @@ -99,7 +96,6 @@ impl PropDef { doc: doc.to_string(), typ: typ.clone(), default: default.clone(), - pref_key: None, gecko_pref: None, string_alias: None, } diff --git a/components/support/nimbus-fml/src/frontend.rs b/components/support/nimbus-fml/src/frontend.rs index cdfd788446e..16d6f57a03d 100644 --- a/components/support/nimbus-fml/src/frontend.rs +++ b/components/support/nimbus-fml/src/frontend.rs @@ -38,10 +38,6 @@ pub(crate) struct FeatureFieldBody { #[serde(flatten)] pub(crate) field: FieldBody, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) pref_key: Option, - #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub(crate) gecko_pref: Option, @@ -348,7 +344,6 @@ impl ManifestFrontEnd { fn get_prop_def_from_feature_field(&self, nm: &str, body: &FeatureFieldBody) -> PropDef { let mut prop = self.get_prop_def_from_field(nm, &body.field); - prop.pref_key.clone_from(&body.pref_key); prop.gecko_pref.clone_from(&body.gecko_pref); if let Some(s) = &body.string_alias { prop.string_alias = Some(TypeRef::StringAlias(s.clone())); @@ -384,7 +379,6 @@ impl ManifestFrontEnd { }, default: json!(body.default), string_alias: None, - pref_key: Default::default(), gecko_pref: Default::default(), } } diff --git a/components/support/nimbus-fml/src/intermediate_representation.rs b/components/support/nimbus-fml/src/intermediate_representation.rs index 71c3c51fc76..60b99fcbb30 100644 --- a/components/support/nimbus-fml/src/intermediate_representation.rs +++ b/components/support/nimbus-fml/src/intermediate_representation.rs @@ -117,18 +117,6 @@ impl Display for TypeRef { } impl TypeRef { - pub(crate) fn supports_prefs(&self) -> bool { - match self { - Self::Boolean | Self::String | Self::Int | Self::StringAlias(_) | Self::BundleText => { - true - } - // There may be a chance that we can get Self::Option to work, but not at this time. - // This may be done by adding a branch to this match and adding a `preference_getter` to - // the `OptionalCodeType`. - _ => false, - } - } - pub(crate) fn supports_gecko_prefs(&self, lax_pref_validation: bool) -> bool { match self { Self::Option(boxed) => matches!(**boxed, Self::Boolean | Self::Int | Self::String), @@ -589,10 +577,6 @@ impl FeatureDef { Value::Object(props) } - pub fn has_prefs(&self) -> bool { - self.props.iter().any(|p| p.has_prefs()) - } - pub fn has_gecko_prefs(&self) -> bool { self.props.iter().any(|p| p.has_gecko_prefs()) } @@ -761,9 +745,6 @@ pub struct PropDef { pub(crate) default: Literal, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) pref_key: Option, - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub(crate) gecko_pref: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] @@ -783,15 +764,9 @@ impl PropDef { pub fn default(&self) -> Literal { self.default.clone() } - pub fn has_prefs(&self) -> bool { - self.pref_key.is_some() && self.typ.supports_prefs() - } pub fn has_gecko_prefs(&self) -> bool { self.gecko_pref.is_some() && self.typ.supports_gecko_prefs(false) } - pub fn pref_key(&self) -> Option { - self.pref_key.clone() - } pub fn gecko_pref(&self) -> Option { self.gecko_pref.clone() } diff --git a/components/support/nimbus-fml/test/pref_overrides.kts b/components/support/nimbus-fml/test/pref_overrides.kts deleted file mode 100644 index f7899317bb3..00000000000 --- a/components/support/nimbus-fml/test/pref_overrides.kts +++ /dev/null @@ -1,66 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import android.content.Context as MockContext -import android.content.SharedPreferences as MockSharedPreferences - -import com.example.nimbus.AppConfig - -import org.mozilla.experiments.nimbus.HardcodedNimbusFeatures -import org.mozilla.experiments.nimbus.FeaturesInterface - -import org.json.JSONObject - -class PrefNimbusFeatures( - override val prefs: MockSharedPreferences, - val nimbus: HardcodedNimbusFeatures, -): FeaturesInterface { - override val context: MockContext = nimbus.context - override fun getVariables(featureId: String, recordExposureEvent: Boolean) = - nimbus.getVariables(featureId, recordExposureEvent) -} - -val context = MockContext() -val prefs = MockSharedPreferences() -val nimbusFromRust = HardcodedNimbusFeatures(context, - "my-feature" to JSONObject(mapOf( - "my-boolean" to false, - "my-int" to 100, - "my-string" to "from json", - "my-text" to "from json" - )) -) - -// Before initialization with hardcoded, just get values from the manifest. -val feature0 = AppConfig.features.myFeature.value() - -assert(feature0.myBoolean == false) -assert(feature0.myInt == 0) -assert(feature0.myString == "from manifest") -assert(feature0.myText == "from manifest") -assert(!feature0.isModified()) - - -val nimbus = PrefNimbusFeatures(prefs, nimbusFromRust) - -AppConfig.initialize { nimbus } - -val feature = AppConfig.features.myFeature.value() - -assert(feature.myBoolean == false) -assert(feature.myInt == 100) -assert(feature.myString == "from json") -assert(feature.myText == "from json") -assert(!feature.isModified()) - -prefs.put("my-boolean-pref-key", true) -prefs.put("my-int-pref-key", 42) -prefs.put("my-string-pref-key", "from pref") -prefs.put("my-text-pref-key", "from pref") - -assert(feature.myBoolean == true) -assert(feature.myInt == 42) -assert(feature.myString == "from pref") -assert(feature.myText == "from pref") -assert(feature.isModified()) diff --git a/components/support/nimbus-fml/test/pref_overrides.swift b/components/support/nimbus-fml/test/pref_overrides.swift deleted file mode 100644 index 73428b7bb35..00000000000 --- a/components/support/nimbus-fml/test/pref_overrides.swift +++ /dev/null @@ -1,83 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public -* License, v. 2.0. If a copy of the MPL was not distributed with this -* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import FeatureManifest -import Foundation - -class PrefNimbusFeatures { - private let _userDefaults: UserDefaults - private let nimbus: HardcodedNimbusFeatures - - init(_ prefs: UserDefaults, _ nimbus: HardcodedNimbusFeatures) { - self._userDefaults = prefs - self.nimbus = nimbus - } -} - -extension PrefNimbusFeatures: FeaturesInterface { - public var userDefaults: UserDefaults? { - get { - _userDefaults - } - } - - public func getVariables(featureId: String, sendExposureEvent: Bool) -> Variables { - return nimbus.getVariables(featureId: featureId, sendExposureEvent: sendExposureEvent) - } - - public func recordExposureEvent(featureId: String, experimentSlug: String?) { - nimbus.recordExposureEvent(featureId: featureId, experimentSlug: experimentSlug) - } - - public func recordMalformedConfiguration(featureId: String, with partId: String) { - nimbus.recordMalformedConfiguration(featureId: featureId, with: partId) - } -} - -// Test the defaults still work. -let feature0 = AppConfig.shared.features.myFeature.value() - -assert(feature0.myBoolean == false) -assert(feature0.myInt == 0) -assert(feature0.myString == "from manifest") -assert(feature0.myText == "from manifest") -assert(!feature0.isModified()) - -// Now test that JSON still has an effect. -let prefs = UserDefaults() -prefs.removeObject(forKey: "my-boolean-pref-key") -prefs.removeObject(forKey: "my-int-pref-key") -prefs.removeObject(forKey: "my-string-pref-key") -prefs.removeObject(forKey: "my-text-pref-key") - -let nimbusFromRust = HardcodedNimbusFeatures(with: - ["my-feature": [ - "my-boolean": false, - "my-int": 100, - "my-string": "from json", - "my-text": "from json" - ]] -) -let nimbus = PrefNimbusFeatures(prefs, nimbusFromRust) -AppConfig.shared.initialize { nimbus } - -let feature = AppConfig.shared.features.myFeature.value() - -assert(feature.myBoolean == false) -assert(feature.myInt == 100) -assert(feature.myString == "from json") -assert(feature.myText == "from json") -assert(!feature.isModified()) - -// Now set with prefs. - -prefs.set(true, forKey: "my-boolean-pref-key") -prefs.set(42, forKey: "my-int-pref-key") -prefs.set("from pref", forKey: "my-string-pref-key") -prefs.set("from pref", forKey: "my-text-pref-key") - -assert(feature.myBoolean == true) -assert(feature.myInt == 42) -assert(feature.myString == "from pref") -assert(feature.myText == "from pref") -assert(feature.isModified())