Skip to content

Bug 1938924 - Add support for search-config-overrides-v2 to the Rust… #6614

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions components/search/src/configuration_overrides_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* 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/. */

//! This module defines the structures that we use for serde_json to parse
//! the search configuration overrides.

use crate::JSONEngineUrls;
use serde::Deserialize;

/// Represents search configuration overrides record.
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct JSONOverridesRecord {
/// This is the identifier of the search engine in search-config-v2 that this
/// record will override. It may be extended by telemetry_suffix.
pub identifier: String,

/// The partner code for the engine or variant. This will be inserted into
/// parameters which include '{partnerCode}
pub partner_code: String,

/// Suffix that is appended to the search engine identifier following a
/// dash, i.e. `<identifier>-<suffix>`. There should always be a suffix
/// supplied if the partner code is different.
pub telemetry_suffix: Option<String>,

/// The url used for reporting clicks.
pub click_url: String,

/// The URLs associated with the search engine.
//pub urls: JSONOverrideEngineUrls,
pub urls: JSONEngineUrls,
}

/// Represents the search configuration overrides as received from remote settings.
#[derive(Debug, Deserialize)]
pub(crate) struct JSONSearchConfigurationOverrides {
pub data: Vec<JSONOverridesRecord>,
}
6 changes: 5 additions & 1 deletion components/search/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ use remote_settings::RemoteSettingsError;
pub enum Error {
#[error("Search configuration not specified")]
SearchConfigNotSpecified,
#[error("No records received from remote settings")]
#[error("Search configuration overrides not specified")]
SearchConfigOverridesNotSpecified,
#[error("No search config v2 records received from remote settings")]
SearchConfigNoRecords,
#[error("No search config overrides v2 records received from remote settings")]
SearchConfigOverridesNoRecords,
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Remote Settings error: {0}")]
Expand Down
97 changes: 90 additions & 7 deletions components/search/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

//! This module defines the functions for managing the filtering of the configuration.

use crate::configuration_overrides_types::JSONOverridesRecord;
use crate::environment_matching::matches_user_environment;
use crate::{
error::Error, JSONDefaultEnginesRecord, JSONEngineBase, JSONEngineRecord, JSONEngineUrl,
Expand Down Expand Up @@ -94,6 +95,16 @@ impl SearchEngineDefinition {
}
}

fn merge_override(&mut self, override_record: &JSONOverridesRecord) {
self.partner_code = override_record.partner_code.clone();
self.urls.merge(&override_record.urls);
self.click_url = Some(override_record.click_url.clone());

if let Some(telemetry_suffix) = &override_record.telemetry_suffix {
self.telemetry_suffix = telemetry_suffix.clone();
}
}

pub(crate) fn from_configuration_details(
identifier: &str,
base: JSONEngineBase,
Expand All @@ -111,6 +122,7 @@ impl SearchEngineDefinition {
partner_code: base.partner_code.unwrap_or_default(),
telemetry_suffix: String::new(),
urls: base.urls.into(),
click_url: None,
};

engine_definition.merge_variant(variant);
Expand All @@ -132,21 +144,33 @@ pub(crate) trait Filter {
fn filter_records(
&self,
user_environment: &SearchUserEnvironment,
overrides: Option<Vec<JSONOverridesRecord>>,
) -> Result<FilterRecordsResult, Error>;
}

fn apply_overrides(engines: &mut [SearchEngineDefinition], overrides: &[JSONOverridesRecord]) {
for override_record in overrides {
for engine in engines.iter_mut() {
if engine.identifier == override_record.identifier {
engine.merge_override(override_record);
}
}
}
}

impl Filter for Vec<RemoteSettingsRecord> {
fn filter_records(
&self,
user_environment: &SearchUserEnvironment,
overrides: Option<Vec<JSONOverridesRecord>>,
) -> Result<FilterRecordsResult, Error> {
let mut engines = Vec::new();
let mut default_engines_record = None;
let mut engine_orders_record = None;

for record in self {
// TODO: Bug 1947241 - Find a way to avoid having to serialise the records
// back to strings and then deserilise them into the records that we want.
// back to strings and then deserialise them into the records that we want.
let stringified = serde_json::to_string(&record.fields)?;
match record.fields.get("recordType") {
Some(val) if *val == "engine" => {
Expand All @@ -171,6 +195,10 @@ impl Filter for Vec<RemoteSettingsRecord> {
}
}

if let Some(overrides_data) = &overrides {
apply_overrides(&mut engines, overrides_data);
}

Ok(FilterRecordsResult {
engines,
default_engines_record,
Expand All @@ -183,6 +211,7 @@ impl Filter for Vec<JSONSearchConfigurationRecords> {
fn filter_records(
&self,
user_environment: &SearchUserEnvironment,
overrides: Option<Vec<JSONOverridesRecord>>,
) -> Result<FilterRecordsResult, Error> {
let mut engines = Vec::new();
let mut default_engines_record = None;
Expand All @@ -206,24 +235,28 @@ impl Filter for Vec<JSONSearchConfigurationRecords> {
}
}

if let Some(overrides_data) = &overrides {
apply_overrides(&mut engines, overrides_data);
}

Ok(FilterRecordsResult {
engines,
default_engines_record: default_engines_record.cloned(),
engine_orders_record: engine_orders_record.cloned(),
})
}
}

pub(crate) fn filter_engine_configuration_impl(
user_environment: SearchUserEnvironment,
configuration: &impl Filter,
overrides: Option<Vec<JSONOverridesRecord>>,
) -> Result<RefinedSearchConfig, Error> {
let mut user_environment = user_environment.clone();
user_environment.locale = user_environment.locale.to_lowercase();
user_environment.region = user_environment.region.to_lowercase();
user_environment.version = user_environment.version.to_lowercase();

let filtered_result = configuration.filter_records(&user_environment);
let filtered_result = configuration.filter_records(&user_environment, overrides);

filtered_result.map(|result| {
let (default_engine_id, default_private_engine_id) = determine_default_engines(
Expand Down Expand Up @@ -381,6 +414,52 @@ mod tests {
use once_cell::sync::Lazy;
use pretty_assertions::assert_eq;

#[test]
fn test_merge_override() {
let mut test_engine = SearchEngineDefinition {
identifier: "test".to_string(),
partner_code: "partner-code".to_string(),
telemetry_suffix: "original-telemetry-suffix".to_string(),
..Default::default()
};

let override_record = JSONOverridesRecord {
identifier: "test".to_string(),
partner_code: "override-partner-code".to_string(),
click_url: "https://example.com/click-url".to_string(),
telemetry_suffix: None,
urls: JSONEngineUrls {
search: Some(JSONEngineUrl {
base: Some("https://example.com/override-search".to_string()),
method: None,
params: None,
search_term_param_name: None,
}),
..Default::default()
},
};

test_engine.merge_override(&override_record);

assert_eq!(
test_engine.partner_code, "override-partner-code",
"Should override the partner code"
);
assert_eq!(
test_engine.click_url,
Some("https://example.com/click-url".to_string()),
"Should override the click url"
);
assert_eq!(
test_engine.urls.search.base, "https://example.com/override-search",
"Should override search url"
);
assert_eq!(
test_engine.telemetry_suffix, "original-telemetry-suffix",
"Should not override telemetry suffix when telemetry suffix is supplied as None"
);
}

#[test]
fn test_from_configuration_details_fallsback_to_defaults() {
// This test doesn't use `..Default::default()` as we want to
Expand Down Expand Up @@ -442,7 +521,8 @@ mod tests {
suggestions: None,
trending: None,
search_form: None
}
},
click_url: None
}
)
}
Expand Down Expand Up @@ -593,7 +673,8 @@ mod tests {
}],
search_term_param_name: None,
}),
}
},
click_url: None
}
)
}
Expand Down Expand Up @@ -719,7 +800,8 @@ mod tests {
}],
search_term_param_name: None,
}),
}
},
click_url: None
}
)
}
Expand Down Expand Up @@ -904,7 +986,8 @@ mod tests {
}],
search_term_param_name: None,
}),
}
},
click_url: None
}
)
}
Expand Down
1 change: 1 addition & 0 deletions components/search/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* 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/. */

mod configuration_overrides_types;
mod configuration_types;
mod environment_matching;
mod error;
Expand Down
Loading