From a81db8b7a5e36ce5f834fedda6a412faa499bf3a Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Mon, 17 Aug 2026 15:25:22 -0700 Subject: [PATCH 1/3] feat: Adds basic nimbus flag implementation --- components/ads-client/src/ffi.rs | 13 ++++++-- components/ads-client/src/lib.rs | 45 ++++++++++++++++++++++++-- components/ads-client/src/nimbus.rs | 49 +++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 components/ads-client/src/nimbus.rs diff --git a/components/ads-client/src/ffi.rs b/components/ads-client/src/ffi.rs index ba50352fdc1..87e56fc4ba1 100644 --- a/components/ads-client/src/ffi.rs +++ b/components/ads-client/src/ffi.rs @@ -20,6 +20,7 @@ use crate::mars::ad_response::{ }; use crate::mars::Environment; use crate::mars::ReportReason; +use crate::nimbus::NimbusFlags; use crate::AdsClientUrl; use crate::MozAdsClient; use parking_lot::Mutex; @@ -107,6 +108,7 @@ struct MozAdsClientBuilderInner { context_id_provider: Option>, environment: Option, telemetry: Option>, + nimbus_flags: Option, } impl Default for MozAdsClientBuilder { @@ -139,9 +141,9 @@ impl MozAdsClientBuilder { .unwrap_or_else(MozAdsTelemetryWrapper::noop), }; let client = AdsClient::new(client_config); - MozAdsClient { - inner: Mutex::new(client), - } + let flags = Arc::new(inner.nimbus_flags.clone().unwrap_or_default()); + let inner = Mutex::new(client); + MozAdsClient { inner, flags } } pub fn cache_config(self: Arc, cache_config: MozAdsCacheConfig) -> Arc { @@ -166,6 +168,11 @@ impl MozAdsClientBuilder { self.0.lock().telemetry = Some(Arc::from(telemetry)); self } + + pub fn nimbus_flags(self: Arc, flags: HashMap) -> Arc { + self.0.lock().nimbus_flags = Some(NimbusFlags::new(flags)); + self + } } #[derive(Clone, Copy, Debug, Default, uniffi::Enum, Eq, PartialEq)] diff --git a/components/ads-client/src/lib.rs b/components/ads-client/src/lib.rs index 87cecb2a5f8..8c0a13b285f 100644 --- a/components/ads-client/src/lib.rs +++ b/components/ads-client/src/lib.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use client::error::ComponentError; use error_support::handle_error; @@ -19,11 +19,15 @@ mod client; mod ffi; pub mod http_cache; mod mars; +pub mod nimbus; pub mod telemetry; pub use ffi::*; -use crate::ffi::telemetry::MozAdsTelemetryWrapper; +use crate::{ + ffi::telemetry::MozAdsTelemetryWrapper, + nimbus::{NimbusFlag, NimbusFlags}, +}; #[cfg(test)] mod test_utils; @@ -39,6 +43,7 @@ uniffi::custom_type!(AdsClientUrl, String, { #[derive(uniffi::Object)] pub struct MozAdsClient { inner: Mutex>, + flags: Arc, } #[uniffi::export] @@ -174,3 +179,39 @@ impl MozAdsClient { Ok(response.into_iter().map(|(k, v)| (k, v.into())).collect()) } } + +impl MozAdsClient { + pub fn check_nimbus_flag(&self, flag: &NimbusFlag) -> bool { + self.flags.check_flag(flag) + } +} + +#[cfg(test)] +impl MozAdsClient { + fn nimbus_test_flag(&self) -> bool { + self.flags.check_flag(&crate::nimbus::NimbusFlag::Test) + } +} + +mod tests { + #[test] + fn nimbus_test_flag_active_on_test() { + let nimbus_flag_off = std::sync::Arc::new(crate::MozAdsClientBuilder::new()) + .environment(crate::MozAdsEnvironment::Test) + .build(); + assert!( + !nimbus_flag_off.nimbus_test_flag(), + "Nimbus `Test` flag should be disabled if no flags are passed" + ); + let mut flags = std::collections::HashMap::new(); + flags.insert("test".to_string(), true); + let nimbus_flag_on = std::sync::Arc::new(crate::MozAdsClientBuilder::new()) + .environment(crate::MozAdsEnvironment::Test) + .nimbus_flags(flags) + .build(); + assert!( + nimbus_flag_on.nimbus_test_flag(), + "Nimbus `Test` flag should be enabled if no flag is passed" + ); + } +} diff --git a/components/ads-client/src/nimbus.rs b/components/ads-client/src/nimbus.rs new file mode 100644 index 00000000000..ac9b87c31e8 --- /dev/null +++ b/components/ads-client/src/nimbus.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; + +// At the current moment, nimbus usage exclusively inside of rust components is not fully realized. +// Therefore, for now we will accept a series of string flags passed in from each surface (that are connected to nimbus). +// The ads-client can then branch behavior based on the passed flags. +#[derive(Clone, Default)] +pub struct NimbusFlags { + flags: HashMap, +} + +impl NimbusFlags { + // Parse and store flags in constructor. + // Allow unrecognized flags to be read as `Unknown`, so that extra irrelevant flags can be easily passed. + pub fn new(flags: HashMap) -> NimbusFlags { + NimbusFlags { + flags: flags + .into_iter() + .map(|(k, v)| (NimbusFlag::from_string(&k), v)) + .collect(), + } + } + + pub fn check_flag(&self, flag: &NimbusFlag) -> bool { + *self.flags.get(flag).unwrap_or(&false) + } +} + +#[derive(Clone, Hash, PartialEq, Eq)] +pub enum NimbusFlag { + // `ads-client.async-enabled` + AsyncEnabled, + + // This allows unrecognized flags to be passed (parsed as `Unknown`) + Unknown(String), + + #[cfg(test)] + Test, +} + +impl NimbusFlag { + pub fn from_string(s: &str) -> NimbusFlag { + match s { + "ads-client.async-enabled" => NimbusFlag::AsyncEnabled, + #[cfg(test)] + "test" => NimbusFlag::Test, + s => NimbusFlag::Unknown(s.to_string()), + } + } +} From 5c72ad2e7170f690a69ff0f5c8839149c38a8565 Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Tue, 18 Aug 2026 02:00:45 -0700 Subject: [PATCH 2/3] fix: Adds changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10f86a56308..34c3362c50f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ [Full Changelog](In progress) +## ✨ What's Changed ✨ + +### Ads-Client + +- Add optional `nimbus_flags(...)` argument to ads-client builder, allowing passed nimbus flags to be read within the rust component itself. + # v155.0 (_2026-08-13_) [Full Changelog](https://github.com/mozilla/application-services/compare/v154.0...v155.0) From 8574318794ce79b7d4e72c6b25395cb26ec28556 Mon Sep 17 00:00:00 2001 From: Wyatt Verchere Date: Tue, 18 Aug 2026 09:07:30 -0700 Subject: [PATCH 3/3] fix: Move test to nimbus --- components/ads-client/src/lib.rs | 23 ----------------------- components/ads-client/src/nimbus.rs | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/components/ads-client/src/lib.rs b/components/ads-client/src/lib.rs index 8c0a13b285f..2f93229b635 100644 --- a/components/ads-client/src/lib.rs +++ b/components/ads-client/src/lib.rs @@ -192,26 +192,3 @@ impl MozAdsClient { self.flags.check_flag(&crate::nimbus::NimbusFlag::Test) } } - -mod tests { - #[test] - fn nimbus_test_flag_active_on_test() { - let nimbus_flag_off = std::sync::Arc::new(crate::MozAdsClientBuilder::new()) - .environment(crate::MozAdsEnvironment::Test) - .build(); - assert!( - !nimbus_flag_off.nimbus_test_flag(), - "Nimbus `Test` flag should be disabled if no flags are passed" - ); - let mut flags = std::collections::HashMap::new(); - flags.insert("test".to_string(), true); - let nimbus_flag_on = std::sync::Arc::new(crate::MozAdsClientBuilder::new()) - .environment(crate::MozAdsEnvironment::Test) - .nimbus_flags(flags) - .build(); - assert!( - nimbus_flag_on.nimbus_test_flag(), - "Nimbus `Test` flag should be enabled if no flag is passed" - ); - } -} diff --git a/components/ads-client/src/nimbus.rs b/components/ads-client/src/nimbus.rs index ac9b87c31e8..ac923f33c38 100644 --- a/components/ads-client/src/nimbus.rs +++ b/components/ads-client/src/nimbus.rs @@ -47,3 +47,26 @@ impl NimbusFlag { } } } + +mod tests { + #[test] + fn nimbus_test_flag_active_on_test() { + let nimbus_flag_off = std::sync::Arc::new(crate::MozAdsClientBuilder::new()) + .environment(crate::MozAdsEnvironment::Test) + .build(); + assert!( + !nimbus_flag_off.nimbus_test_flag(), + "Nimbus `Test` flag should be disabled if no flags are passed" + ); + let mut flags = std::collections::HashMap::new(); + flags.insert("test".to_string(), true); + let nimbus_flag_on = std::sync::Arc::new(crate::MozAdsClientBuilder::new()) + .environment(crate::MozAdsEnvironment::Test) + .nimbus_flags(flags) + .build(); + assert!( + nimbus_flag_on.nimbus_test_flag(), + "Nimbus `Test` flag should be enabled if no flag is passed" + ); + } +}