1use crate::configuration_overrides_types::JSONOverridesRecord;
8use crate::environment_matching::matches_user_environment;
9use crate::{
10 error::Error, JSONDefaultEnginesRecord, JSONEngineBase, JSONEngineMethod, JSONEngineRecord,
11 JSONEngineUrl, JSONEngineUrls, JSONEngineVariant, JSONSearchConfigurationRecords,
12 RefinedSearchConfig, SearchEngineDefinition, SearchEngineUrl, SearchEngineUrls,
13 SearchUserEnvironment,
14};
15use crate::{sort_helpers, JSONAvailableLocalesRecord, JSONEngineOrdersRecord};
16use remote_settings::RemoteSettingsRecord;
17use std::collections::HashSet;
18
19impl Default for SearchEngineUrl {
20 fn default() -> Self {
21 Self {
22 base: Default::default(),
23 method: JSONEngineMethod::default().as_str().to_string(),
24 params: Default::default(),
25 search_term_param_name: Default::default(),
26 display_name: Default::default(),
27 is_new_until: Default::default(),
28 exclude_partner_code_from_telemetry: Default::default(),
29 }
30 }
31}
32
33impl SearchEngineUrl {
34 fn merge(&mut self, user_environment: &SearchUserEnvironment, preferred: &JSONEngineUrl) {
35 if let Some(base) = &preferred.base {
36 self.base = base.clone();
37 }
38 if let Some(method) = &preferred.method {
39 self.method = method.as_str().to_string();
40 }
41 if let Some(params) = &preferred.params {
42 self.params = params.clone();
43 }
44 if let Some(search_term_param_name) = &preferred.search_term_param_name {
45 self.search_term_param_name = Some(search_term_param_name.clone());
46 }
47 if let Some(display_name_map) = &preferred.display_name_map {
48 self.display_name = display_name_map
49 .get(&user_environment.locale)
50 .or_else(|| display_name_map.get("default"))
51 .cloned();
52 }
53 if let Some(is_new_until) = &preferred.is_new_until {
54 self.is_new_until = Some(is_new_until.clone());
55 }
56 self.exclude_partner_code_from_telemetry = preferred.exclude_partner_code_from_telemetry;
57 }
58}
59
60impl SearchEngineUrls {
61 fn merge(&mut self, user_environment: &SearchUserEnvironment, preferred: &JSONEngineUrls) {
62 if let Some(search_url) = &preferred.search {
63 self.search.merge(user_environment, search_url);
64 }
65 if let Some(suggestions) = &preferred.suggestions {
66 self.suggestions
67 .get_or_insert_with(Default::default)
68 .merge(user_environment, suggestions);
69 }
70 if let Some(trending) = &preferred.trending {
71 self.trending
72 .get_or_insert_with(Default::default)
73 .merge(user_environment, trending);
74 }
75 if let Some(search_form) = &preferred.search_form {
76 self.search_form
77 .get_or_insert_with(Default::default)
78 .merge(user_environment, search_form);
79 }
80 if let Some(visual_search) = &preferred.visual_search {
81 self.visual_search
82 .get_or_insert_with(Default::default)
83 .merge(user_environment, visual_search);
84 }
85 }
86}
87
88impl SearchEngineDefinition {
89 fn merge_variant(
90 &mut self,
91 user_environment: &SearchUserEnvironment,
92 variant: &JSONEngineVariant,
93 ) {
94 if !self.optional {
95 self.optional = variant.optional;
96 }
97 if let Some(partner_code) = &variant.partner_code {
98 self.partner_code = partner_code.clone();
99 }
100 if let Some(telemetry_suffix) = &variant.telemetry_suffix {
101 self.telemetry_suffix = telemetry_suffix.clone();
102 }
103 if let Some(urls) = &variant.urls {
104 self.urls.merge(user_environment, urls);
105 }
106 if let Some(is_new_until) = &variant.is_new_until {
107 self.is_new_until = Some(is_new_until.clone());
108 }
109 }
110
111 fn merge_override(
112 &mut self,
113 user_environment: &SearchUserEnvironment,
114 override_record: &JSONOverridesRecord,
115 ) {
116 self.partner_code = override_record.partner_code.clone();
117 self.urls.merge(user_environment, &override_record.urls);
118 self.click_url = Some(override_record.click_url.clone());
119
120 if let Some(telemetry_suffix) = &override_record.telemetry_suffix {
121 self.telemetry_suffix = telemetry_suffix.clone();
122 }
123 }
124
125 pub(crate) fn from_configuration_details(
126 user_environment: &SearchUserEnvironment,
127 identifier: &str,
128 base: JSONEngineBase,
129 variant: &JSONEngineVariant,
130 sub_variant: &Option<JSONEngineVariant>,
131 ) -> SearchEngineDefinition {
132 let mut engine_definition = SearchEngineDefinition {
133 aliases: base.aliases.unwrap_or_default(),
134 charset: base.charset.unwrap_or_else(|| "UTF-8".to_string()),
135 classification: base.classification,
136 identifier: identifier.to_string(),
137 name: base.name,
138 optional: variant.optional,
139 order_hint: None,
140 partner_code: base.partner_code.unwrap_or_default(),
141 telemetry_suffix: String::new(),
142 urls: SearchEngineUrls::default(),
143 click_url: None,
144 is_new_until: None,
145 };
146
147 engine_definition.urls.merge(user_environment, &base.urls);
148 engine_definition.merge_variant(user_environment, variant);
149 if let Some(sub_variant) = sub_variant {
150 engine_definition.merge_variant(user_environment, sub_variant);
151 }
152
153 engine_definition
154 }
155}
156
157pub(crate) struct FilterRecordsResult {
158 engines: Vec<SearchEngineDefinition>,
159 default_engines_record: Option<JSONDefaultEnginesRecord>,
160 engine_orders_record: Option<JSONEngineOrdersRecord>,
161}
162
163pub(crate) trait Filter {
164 fn filter_records(
165 &self,
166 user_environment: &mut SearchUserEnvironment,
167 overrides: Option<Vec<JSONOverridesRecord>>,
168 ) -> Result<FilterRecordsResult, Error>;
169}
170
171fn apply_overrides(
172 user_environment: &SearchUserEnvironment,
173 engines: &mut [SearchEngineDefinition],
174 overrides: &[JSONOverridesRecord],
175) {
176 for override_record in overrides {
177 for engine in engines.iter_mut() {
178 if engine.identifier == override_record.identifier {
179 engine.merge_override(user_environment, override_record);
180 }
181 }
182 }
183}
184
185fn negotiate_languages(user_environment: &mut SearchUserEnvironment, available_locales: &[String]) {
186 let user_locale = user_environment.locale.to_lowercase();
187
188 let available_locales_set: HashSet<String> = available_locales
189 .iter()
190 .map(|locale| locale.to_lowercase())
191 .collect();
192
193 if available_locales_set.contains(&user_locale) {
194 return;
195 }
196 if user_locale.starts_with("en-") {
197 user_environment.locale = "en-us".to_string();
198 return;
199 }
200 if let Some(index) = user_locale.find('-') {
201 let base_locale = &user_locale[..index];
202 if available_locales_set.contains(base_locale) {
203 user_environment.locale = base_locale.to_string();
204 }
205 }
206}
207
208impl Filter for Vec<RemoteSettingsRecord> {
209 fn filter_records(
210 &self,
211 user_environment: &mut SearchUserEnvironment,
212 overrides: Option<Vec<JSONOverridesRecord>>,
213 ) -> Result<FilterRecordsResult, Error> {
214 let mut available_locales = Vec::new();
215 for record in self {
216 if let Some(val) = record.fields.get("recordType") {
217 if *val == "availableLocales" {
218 let stringified = serde_json::to_string(&record.fields)?;
219 let locales_record: Option<JSONAvailableLocalesRecord> =
220 serde_json::from_str(&stringified)?;
221 available_locales = locales_record.unwrap().locales;
222 }
223 }
224 }
225 negotiate_languages(user_environment, &available_locales);
226
227 let mut engines = Vec::new();
228 let mut default_engines_record = None;
229 let mut engine_orders_record = None;
230
231 for record in self {
232 let stringified = serde_json::to_string(&record.fields)?;
235 match record.fields.get("recordType") {
236 Some(val) if *val == "engine" => {
237 let engine_config: Option<JSONEngineRecord> =
238 serde_json::from_str(&stringified)?;
239 if let Some(engine_config) = engine_config {
240 let result =
241 maybe_extract_engine_config(user_environment, Box::new(engine_config));
242 engines.extend(result);
243 }
244 }
245 Some(val) if *val == "defaultEngines" => {
246 default_engines_record = serde_json::from_str(&stringified)?;
247 }
248 Some(val) if *val == "engineOrders" => {
249 engine_orders_record = serde_json::from_str(&stringified)?;
250 }
251 Some(val) if *val == "availableLocales" => {
252 }
254 Some(_val) => {}
257 None => {}
258 }
259 }
260
261 if let Some(overrides_data) = &overrides {
262 apply_overrides(user_environment, &mut engines, overrides_data);
263 }
264
265 Ok(FilterRecordsResult {
266 engines,
267 default_engines_record,
268 engine_orders_record,
269 })
270 }
271}
272
273impl Filter for Vec<JSONSearchConfigurationRecords> {
274 fn filter_records(
275 &self,
276 user_environment: &mut SearchUserEnvironment,
277 overrides: Option<Vec<JSONOverridesRecord>>,
278 ) -> Result<FilterRecordsResult, Error> {
279 let mut available_locales = Vec::new();
280 for record in self {
281 if let JSONSearchConfigurationRecords::AvailableLocales(locales_record) = record {
282 available_locales = locales_record.locales.clone();
283 }
284 }
285 negotiate_languages(user_environment, &available_locales);
286
287 let mut engines = Vec::new();
288 let mut default_engines_record = None;
289 let mut engine_orders_record = None;
290
291 for record in self {
292 match record {
293 JSONSearchConfigurationRecords::Engine(engine) => {
294 let result = maybe_extract_engine_config(user_environment, engine.clone());
295 engines.extend(result);
296 }
297 JSONSearchConfigurationRecords::DefaultEngines(default_engines) => {
298 default_engines_record = Some(default_engines);
299 }
300 JSONSearchConfigurationRecords::EngineOrders(engine_orders) => {
301 engine_orders_record = Some(engine_orders)
302 }
303 JSONSearchConfigurationRecords::AvailableLocales(_) => {
304 }
306 JSONSearchConfigurationRecords::Unknown => {
307 }
309 }
310 }
311
312 if let Some(overrides_data) = &overrides {
313 apply_overrides(user_environment, &mut engines, overrides_data);
314 }
315
316 Ok(FilterRecordsResult {
317 engines,
318 default_engines_record: default_engines_record.cloned(),
319 engine_orders_record: engine_orders_record.cloned(),
320 })
321 }
322}
323
324pub(crate) fn filter_engine_configuration_impl(
325 user_environment: SearchUserEnvironment,
326 configuration: &impl Filter,
327 overrides: Option<Vec<JSONOverridesRecord>>,
328) -> Result<RefinedSearchConfig, Error> {
329 let mut user_environment = user_environment.clone();
330 user_environment.locale = user_environment.locale.to_lowercase();
331 user_environment.region = user_environment.region.to_lowercase();
332 user_environment.version = user_environment.version.to_lowercase();
333
334 let filtered_result = configuration.filter_records(&mut user_environment, overrides);
335
336 filtered_result.map(|result| {
337 let (default_engine_id, default_private_engine_id) = determine_default_engines(
338 &result.engines,
339 result.default_engines_record,
340 &user_environment,
341 );
342
343 let mut engines = result.engines.clone();
344
345 if let Some(orders_record) = result.engine_orders_record {
346 for order_data in &orders_record.orders {
347 if matches_user_environment(&order_data.environment, &user_environment) {
348 sort_helpers::set_engine_order(&mut engines, &order_data.order);
349 }
350 }
351 }
352
353 engines.sort_by(|a, b| {
354 sort_helpers::sort(
355 default_engine_id.as_ref(),
356 default_private_engine_id.as_ref(),
357 a,
358 b,
359 )
360 });
361
362 RefinedSearchConfig {
363 engines,
364 app_default_engine_id: default_engine_id,
365 app_private_default_engine_id: default_private_engine_id,
366 }
367 })
368}
369
370fn maybe_extract_engine_config(
371 user_environment: &SearchUserEnvironment,
372 record: Box<JSONEngineRecord>,
373) -> Option<SearchEngineDefinition> {
374 let JSONEngineRecord {
375 identifier,
376 variants,
377 base,
378 } = *record;
379 let matching_variant = variants
380 .into_iter()
381 .rev()
382 .find(|r| matches_user_environment(&r.environment, user_environment));
383
384 let mut matching_sub_variant = None;
385 if let Some(variant) = &matching_variant {
386 matching_sub_variant = variant
387 .sub_variants
388 .iter()
389 .rev()
390 .find(|r| matches_user_environment(&r.environment, user_environment))
391 .cloned();
392 }
393
394 matching_variant.map(|variant| {
395 SearchEngineDefinition::from_configuration_details(
396 user_environment,
397 &identifier,
398 base,
399 &variant,
400 &matching_sub_variant,
401 )
402 })
403}
404
405fn determine_default_engines(
406 engines: &[SearchEngineDefinition],
407 default_engines_record: Option<JSONDefaultEnginesRecord>,
408 user_environment: &SearchUserEnvironment,
409) -> (Option<String>, Option<String>) {
410 match default_engines_record {
411 None => (None, None),
412 Some(record) => {
413 let mut default_engine_id = None;
414 let mut default_engine_private_id = None;
415
416 let specific_default = record
417 .specific_defaults
418 .into_iter()
419 .rev()
420 .find(|r| matches_user_environment(&r.environment, user_environment));
421
422 if let Some(specific_default) = specific_default {
423 if let Some(engine_id) =
426 find_engine_id_with_match(engines, specific_default.default)
427 {
428 default_engine_id.replace(engine_id);
429 }
430 if let Some(private_engine_id) =
431 find_engine_id_with_match(engines, specific_default.default_private)
432 {
433 default_engine_private_id.replace(private_engine_id);
434 }
435 }
436
437 (
438 default_engine_id.or_else(|| find_engine_id(engines, record.global_default)),
447 default_engine_private_id
448 .or_else(|| find_engine_id(engines, record.global_default_private)),
449 )
450 }
451 }
452}
453
454fn find_engine_id(engines: &[SearchEngineDefinition], engine_id: String) -> Option<String> {
455 if engine_id.is_empty() {
456 return None;
457 }
458 match engines.iter().any(|e| e.identifier == engine_id) {
459 true => Some(engine_id.clone()),
460 false => None,
461 }
462}
463
464fn find_engine_id_with_match(
465 engines: &[SearchEngineDefinition],
466 engine_id_match: String,
467) -> Option<String> {
468 if engine_id_match.is_empty() {
469 return None;
470 }
471 if let Some(match_no_star) = engine_id_match.strip_suffix('*') {
472 return engines
473 .iter()
474 .find(|e| e.identifier.starts_with(match_no_star))
475 .map(|e| e.identifier.clone());
476 }
477
478 engines
479 .iter()
480 .find(|e| e.identifier == engine_id_match)
481 .map(|e| e.identifier.clone())
482}
483
484#[cfg(test)]
485mod tests {
486 use std::{collections::HashMap, vec};
487
488 use super::*;
489 use crate::*;
490 use once_cell::sync::Lazy;
491
492 #[test]
493 fn test_default_search_engine_url() {
494 assert_eq!(
495 SearchEngineUrl::default(),
496 SearchEngineUrl {
497 base: "".to_string(),
498 method: "GET".to_string(),
499 params: Vec::new(),
500 search_term_param_name: None,
501 display_name: None,
502 is_new_until: None,
503 exclude_partner_code_from_telemetry: false,
504 },
505 );
506 }
507
508 #[test]
509 fn test_default_search_engine_urls() {
510 assert_eq!(
511 SearchEngineUrls::default(),
512 SearchEngineUrls {
513 search: SearchEngineUrl::default(),
514 suggestions: None,
515 trending: None,
516 search_form: None,
517 visual_search: None,
518 },
519 );
520 }
521
522 #[test]
523 fn test_merge_override() {
524 let mut test_engine = SearchEngineDefinition {
525 identifier: "test".to_string(),
526 partner_code: "partner-code".to_string(),
527 telemetry_suffix: "original-telemetry-suffix".to_string(),
528 ..Default::default()
529 };
530
531 let override_record = JSONOverridesRecord {
532 identifier: "test".to_string(),
533 partner_code: "override-partner-code".to_string(),
534 click_url: "https://example.com/click-url".to_string(),
535 telemetry_suffix: None,
536 urls: JSONEngineUrls {
537 search: Some(JSONEngineUrl {
538 base: Some("https://example.com/override-search".to_string()),
539 ..Default::default()
540 }),
541 ..Default::default()
542 },
543 };
544
545 test_engine.merge_override(
546 &SearchUserEnvironment {
547 locale: "fi".into(),
548 ..Default::default()
549 },
550 &override_record,
551 );
552
553 assert_eq!(
554 test_engine.partner_code, "override-partner-code",
555 "Should override the partner code"
556 );
557 assert_eq!(
558 test_engine.click_url,
559 Some("https://example.com/click-url".to_string()),
560 "Should override the click url"
561 );
562 assert_eq!(
563 test_engine.urls.search.base, "https://example.com/override-search",
564 "Should override search url"
565 );
566 assert_eq!(
567 test_engine.telemetry_suffix, "original-telemetry-suffix",
568 "Should not override telemetry suffix when telemetry suffix is supplied as None"
569 );
570 }
571
572 #[test]
573 fn test_merge_override_locale_match() {
574 let mut test_engine = SearchEngineDefinition {
575 identifier: "test".to_string(),
576 partner_code: "partner-code".to_string(),
577 telemetry_suffix: "original-telemetry-suffix".to_string(),
578 ..Default::default()
579 };
580
581 let override_record = JSONOverridesRecord {
582 identifier: "test".to_string(),
583 partner_code: "override-partner-code".to_string(),
584 click_url: "https://example.com/click-url".to_string(),
585 telemetry_suffix: None,
586 urls: JSONEngineUrls {
587 search: Some(JSONEngineUrl {
588 base: Some("https://example.com/override-search".to_string()),
589 display_name_map: Some(HashMap::from([
590 ("default".to_string(), "My Display Name".to_string()),
592 ("en-GB".to_string(), "en-GB Display Name".to_string()),
594 ])),
595 ..Default::default()
596 }),
597 ..Default::default()
598 },
599 };
600
601 test_engine.merge_override(
602 &SearchUserEnvironment {
603 locale: "en-GB".into(),
605 ..Default::default()
606 },
607 &override_record,
608 );
609
610 assert_eq!(
611 test_engine.urls.search.display_name,
612 Some("en-GB Display Name".to_string()),
613 "Should override display name with en-GB version"
614 );
615 }
616
617 #[test]
618 fn test_from_configuration_details_fallsback_to_defaults() {
619 let result = SearchEngineDefinition::from_configuration_details(
623 &SearchUserEnvironment {
624 locale: "fi".into(),
625 ..Default::default()
626 },
627 "test",
628 JSONEngineBase {
629 aliases: None,
630 charset: None,
631 classification: SearchEngineClassification::General,
632 name: "Test".to_string(),
633 partner_code: None,
634 urls: JSONEngineUrls {
635 search: Some(JSONEngineUrl {
636 base: Some("https://example.com".to_string()),
637 ..Default::default()
638 }),
639 suggestions: None,
640 trending: None,
641 search_form: None,
642 visual_search: None,
643 },
644 },
645 &JSONEngineVariant {
646 environment: JSONVariantEnvironment {
647 all_regions_and_locales: true,
648 ..Default::default()
649 },
650 is_new_until: None,
651 optional: false,
652 partner_code: None,
653 telemetry_suffix: None,
654 urls: None,
655 sub_variants: vec![],
656 },
657 &None,
658 );
659
660 assert_eq!(
661 result,
662 SearchEngineDefinition {
663 aliases: Vec::new(),
664 charset: "UTF-8".to_string(),
665 classification: SearchEngineClassification::General,
666 identifier: "test".to_string(),
667 is_new_until: None,
668 partner_code: String::new(),
669 name: "Test".to_string(),
670 optional: false,
671 order_hint: None,
672 telemetry_suffix: String::new(),
673 urls: SearchEngineUrls {
674 search: SearchEngineUrl {
675 base: "https://example.com".to_string(),
676 ..Default::default()
677 },
678 suggestions: None,
679 trending: None,
680 search_form: None,
681 visual_search: None,
682 },
683 click_url: None
684 }
685 )
686 }
687
688 static ENGINE_BASE: Lazy<JSONEngineBase> = Lazy::new(|| JSONEngineBase {
689 aliases: Some(vec!["foo".to_string(), "bar".to_string()]),
690 charset: Some("ISO-8859-15".to_string()),
691 classification: SearchEngineClassification::Unknown,
692 name: "Test".to_string(),
693 partner_code: Some("firefox".to_string()),
694 urls: JSONEngineUrls {
695 search: Some(JSONEngineUrl {
696 base: Some("https://example.com".to_string()),
697 method: Some(crate::JSONEngineMethod::Post),
698 params: Some(vec![
699 SearchUrlParam {
700 name: "param".to_string(),
701 value: Some("test param".to_string()),
702 enterprise_value: None,
703 experiment_config: None,
704 },
705 SearchUrlParam {
706 name: "enterprise-name".to_string(),
707 value: None,
708 enterprise_value: Some("enterprise-value".to_string()),
709 experiment_config: None,
710 },
711 ]),
712 search_term_param_name: Some("baz".to_string()),
713 ..Default::default()
714 }),
715 suggestions: Some(JSONEngineUrl {
716 base: Some("https://example.com/suggestions".to_string()),
717 method: Some(crate::JSONEngineMethod::Get),
718 params: Some(vec![SearchUrlParam {
719 name: "suggest-name".to_string(),
720 value: None,
721 enterprise_value: None,
722 experiment_config: Some("suggest-experiment-value".to_string()),
723 }]),
724 search_term_param_name: Some("suggest".to_string()),
725 ..Default::default()
726 }),
727 trending: Some(JSONEngineUrl {
728 base: Some("https://example.com/trending".to_string()),
729 method: Some(crate::JSONEngineMethod::Get),
730 params: Some(vec![SearchUrlParam {
731 name: "trend-name".to_string(),
732 value: Some("trend-value".to_string()),
733 enterprise_value: None,
734 experiment_config: None,
735 }]),
736 ..Default::default()
737 }),
738 search_form: Some(JSONEngineUrl {
739 base: Some("https://example.com/search_form".to_string()),
740 method: Some(crate::JSONEngineMethod::Get),
741 params: Some(vec![SearchUrlParam {
742 name: "search-form-name".to_string(),
743 value: Some("search-form-value".to_string()),
744 enterprise_value: None,
745 experiment_config: None,
746 }]),
747 ..Default::default()
748 }),
749 visual_search: Some(JSONEngineUrl {
750 base: Some("https://example.com/visual_search".to_string()),
751 method: Some(crate::JSONEngineMethod::Get),
752 params: Some(vec![SearchUrlParam {
753 name: "visual-search-name".to_string(),
754 value: Some("visual-search-value".to_string()),
755 enterprise_value: None,
756 experiment_config: None,
757 }]),
758 search_term_param_name: Some("url".to_string()),
759 display_name_map: Some(HashMap::from([
760 ("default".to_string(), "Visual Search".to_string()),
762 ("en-GB".to_string(), "Visual Search en-GB".to_string()),
764 ])),
765 is_new_until: Some("2095-01-01".to_string()),
766 exclude_partner_code_from_telemetry: true,
767 }),
768 },
769 });
770
771 #[test]
772 fn test_from_configuration_details_uses_values() {
773 let result = SearchEngineDefinition::from_configuration_details(
774 &SearchUserEnvironment {
775 locale: "fi".into(),
776 ..Default::default()
777 },
778 "test",
779 Lazy::force(&ENGINE_BASE).clone(),
780 &JSONEngineVariant {
781 environment: JSONVariantEnvironment {
782 all_regions_and_locales: true,
783 ..Default::default()
784 },
785 is_new_until: None,
786 optional: false,
787 partner_code: None,
788 telemetry_suffix: None,
789 urls: None,
790 sub_variants: vec![],
791 },
792 &None,
793 );
794
795 assert_eq!(
796 result,
797 SearchEngineDefinition {
798 aliases: vec!["foo".to_string(), "bar".to_string()],
799 charset: "ISO-8859-15".to_string(),
800 classification: SearchEngineClassification::Unknown,
801 identifier: "test".to_string(),
802 is_new_until: None,
803 partner_code: "firefox".to_string(),
804 name: "Test".to_string(),
805 optional: false,
806 order_hint: None,
807 telemetry_suffix: String::new(),
808 urls: SearchEngineUrls {
809 search: SearchEngineUrl {
810 base: "https://example.com".to_string(),
811 method: "POST".to_string(),
812 params: vec![
813 SearchUrlParam {
814 name: "param".to_string(),
815 value: Some("test param".to_string()),
816 enterprise_value: None,
817 experiment_config: None,
818 },
819 SearchUrlParam {
820 name: "enterprise-name".to_string(),
821 value: None,
822 enterprise_value: Some("enterprise-value".to_string()),
823 experiment_config: None,
824 },
825 ],
826 search_term_param_name: Some("baz".to_string()),
827 ..Default::default()
828 },
829 suggestions: Some(SearchEngineUrl {
830 base: "https://example.com/suggestions".to_string(),
831 method: "GET".to_string(),
832 params: vec![SearchUrlParam {
833 name: "suggest-name".to_string(),
834 value: None,
835 enterprise_value: None,
836 experiment_config: Some("suggest-experiment-value".to_string()),
837 }],
838 search_term_param_name: Some("suggest".to_string()),
839 ..Default::default()
840 }),
841 trending: Some(SearchEngineUrl {
842 base: "https://example.com/trending".to_string(),
843 method: "GET".to_string(),
844 params: vec![SearchUrlParam {
845 name: "trend-name".to_string(),
846 value: Some("trend-value".to_string()),
847 enterprise_value: None,
848 experiment_config: None,
849 }],
850 ..Default::default()
851 }),
852 search_form: Some(SearchEngineUrl {
853 base: "https://example.com/search_form".to_string(),
854 method: "GET".to_string(),
855 params: vec![SearchUrlParam {
856 name: "search-form-name".to_string(),
857 value: Some("search-form-value".to_string()),
858 enterprise_value: None,
859 experiment_config: None,
860 }],
861 ..Default::default()
862 }),
863 visual_search: Some(SearchEngineUrl {
864 base: "https://example.com/visual_search".to_string(),
865 method: "GET".to_string(),
866 params: vec![SearchUrlParam {
867 name: "visual-search-name".to_string(),
868 value: Some("visual-search-value".to_string()),
869 enterprise_value: None,
870 experiment_config: None,
871 }],
872 search_term_param_name: Some("url".to_string()),
873 display_name: Some("Visual Search".to_string()),
874 is_new_until: Some("2095-01-01".to_string()),
875 exclude_partner_code_from_telemetry: true,
876 }),
877 },
878 click_url: None
879 }
880 )
881 }
882
883 #[test]
884 fn test_from_configuration_details_uses_values_locale_match() {
885 let result = SearchEngineDefinition::from_configuration_details(
886 &SearchUserEnvironment {
887 locale: "en-GB".into(),
889 ..Default::default()
890 },
891 "test",
892 Lazy::force(&ENGINE_BASE).clone(),
893 &JSONEngineVariant {
894 environment: JSONVariantEnvironment {
895 all_regions_and_locales: true,
896 ..Default::default()
897 },
898 is_new_until: None,
899 optional: false,
900 partner_code: None,
901 telemetry_suffix: None,
902 urls: None,
903 sub_variants: vec![],
904 },
905 &None,
906 );
907
908 assert_eq!(
909 result,
910 SearchEngineDefinition {
911 aliases: vec!["foo".to_string(), "bar".to_string()],
912 charset: "ISO-8859-15".to_string(),
913 classification: SearchEngineClassification::Unknown,
914 identifier: "test".to_string(),
915 is_new_until: None,
916 partner_code: "firefox".to_string(),
917 name: "Test".to_string(),
918 optional: false,
919 order_hint: None,
920 telemetry_suffix: String::new(),
921 urls: SearchEngineUrls {
922 search: SearchEngineUrl {
923 base: "https://example.com".to_string(),
924 method: "POST".to_string(),
925 params: vec![
926 SearchUrlParam {
927 name: "param".to_string(),
928 value: Some("test param".to_string()),
929 enterprise_value: None,
930 experiment_config: None,
931 },
932 SearchUrlParam {
933 name: "enterprise-name".to_string(),
934 value: None,
935 enterprise_value: Some("enterprise-value".to_string()),
936 experiment_config: None,
937 },
938 ],
939 search_term_param_name: Some("baz".to_string()),
940 ..Default::default()
941 },
942 suggestions: Some(SearchEngineUrl {
943 base: "https://example.com/suggestions".to_string(),
944 method: "GET".to_string(),
945 params: vec![SearchUrlParam {
946 name: "suggest-name".to_string(),
947 value: None,
948 enterprise_value: None,
949 experiment_config: Some("suggest-experiment-value".to_string()),
950 }],
951 search_term_param_name: Some("suggest".to_string()),
952 ..Default::default()
953 }),
954 trending: Some(SearchEngineUrl {
955 base: "https://example.com/trending".to_string(),
956 method: "GET".to_string(),
957 params: vec![SearchUrlParam {
958 name: "trend-name".to_string(),
959 value: Some("trend-value".to_string()),
960 enterprise_value: None,
961 experiment_config: None,
962 }],
963 ..Default::default()
964 }),
965 search_form: Some(SearchEngineUrl {
966 base: "https://example.com/search_form".to_string(),
967 method: "GET".to_string(),
968 params: vec![SearchUrlParam {
969 name: "search-form-name".to_string(),
970 value: Some("search-form-value".to_string()),
971 enterprise_value: None,
972 experiment_config: None,
973 }],
974 ..Default::default()
975 }),
976 visual_search: Some(SearchEngineUrl {
977 base: "https://example.com/visual_search".to_string(),
978 method: "GET".to_string(),
979 params: vec![SearchUrlParam {
980 name: "visual-search-name".to_string(),
981 value: Some("visual-search-value".to_string()),
982 enterprise_value: None,
983 experiment_config: None,
984 }],
985 search_term_param_name: Some("url".to_string()),
986 display_name: Some("Visual Search en-GB".to_string()),
989 is_new_until: Some("2095-01-01".to_string()),
990 exclude_partner_code_from_telemetry: true,
991 }),
992 },
993 click_url: None
994 }
995 )
996 }
997
998 static ENGINE_VARIANT: Lazy<JSONEngineVariant> = Lazy::new(|| JSONEngineVariant {
999 environment: JSONVariantEnvironment {
1000 all_regions_and_locales: true,
1001 ..Default::default()
1002 },
1003 is_new_until: Some("2063-04-05".to_string()),
1004 optional: true,
1005 partner_code: Some("trek".to_string()),
1006 telemetry_suffix: Some("star".to_string()),
1007 urls: Some(JSONEngineUrls {
1008 search: Some(JSONEngineUrl {
1009 base: Some("https://example.com/variant".to_string()),
1010 method: Some(JSONEngineMethod::Get),
1011 params: Some(vec![SearchUrlParam {
1012 name: "variant".to_string(),
1013 value: Some("test variant".to_string()),
1014 enterprise_value: None,
1015 experiment_config: None,
1016 }]),
1017 search_term_param_name: Some("ship".to_string()),
1018 ..Default::default()
1019 }),
1020 suggestions: Some(JSONEngineUrl {
1021 base: Some("https://example.com/suggestions-variant".to_string()),
1022 method: Some(JSONEngineMethod::Get),
1023 params: Some(vec![SearchUrlParam {
1024 name: "suggest-variant".to_string(),
1025 value: Some("sugg test variant".to_string()),
1026 enterprise_value: None,
1027 experiment_config: None,
1028 }]),
1029 search_term_param_name: Some("variant".to_string()),
1030 ..Default::default()
1031 }),
1032 trending: Some(JSONEngineUrl {
1033 base: Some("https://example.com/trending-variant".to_string()),
1034 method: Some(JSONEngineMethod::Get),
1035 params: Some(vec![SearchUrlParam {
1036 name: "trend-variant".to_string(),
1037 value: Some("trend test variant".to_string()),
1038 enterprise_value: None,
1039 experiment_config: None,
1040 }]),
1041 search_term_param_name: Some("trend".to_string()),
1042 exclude_partner_code_from_telemetry: true,
1043 ..Default::default()
1044 }),
1045 search_form: Some(JSONEngineUrl {
1046 base: Some("https://example.com/search_form".to_string()),
1047 method: Some(crate::JSONEngineMethod::Get),
1048 params: Some(vec![SearchUrlParam {
1049 name: "search-form-name".to_string(),
1050 value: Some("search-form-value".to_string()),
1051 enterprise_value: None,
1052 experiment_config: None,
1053 }]),
1054 ..Default::default()
1055 }),
1056 visual_search: Some(JSONEngineUrl {
1057 base: Some("https://example.com/visual-search-variant".to_string()),
1058 method: Some(JSONEngineMethod::Get),
1059 params: Some(vec![SearchUrlParam {
1060 name: "visual-search-variant-name".to_string(),
1061 value: Some("visual-search-variant-value".to_string()),
1062 enterprise_value: None,
1063 experiment_config: None,
1064 }]),
1065 search_term_param_name: Some("url_variant".to_string()),
1066 display_name_map: Some(HashMap::from([
1067 ("default".to_string(), "Visual Search Variant".to_string()),
1068 (
1070 "en-GB".to_string(),
1071 "Visual Search Variant en-GB".to_string(),
1072 ),
1073 ])),
1074 is_new_until: Some("2096-02-02".to_string()),
1075 ..Default::default()
1076 }),
1077 }),
1078 sub_variants: vec![],
1079 });
1080
1081 #[test]
1082 fn test_from_configuration_details_merges_variants() {
1083 let result = SearchEngineDefinition::from_configuration_details(
1084 &SearchUserEnvironment {
1085 locale: "fi".into(),
1086 ..Default::default()
1087 },
1088 "test",
1089 Lazy::force(&ENGINE_BASE).clone(),
1090 &ENGINE_VARIANT,
1091 &None,
1092 );
1093
1094 assert_eq!(
1095 result,
1096 SearchEngineDefinition {
1097 aliases: vec!["foo".to_string(), "bar".to_string()],
1098 charset: "ISO-8859-15".to_string(),
1099 classification: SearchEngineClassification::Unknown,
1100 identifier: "test".to_string(),
1101 is_new_until: Some("2063-04-05".to_string()),
1102 partner_code: "trek".to_string(),
1103 name: "Test".to_string(),
1104 optional: true,
1105 order_hint: None,
1106 telemetry_suffix: "star".to_string(),
1107 urls: SearchEngineUrls {
1108 search: SearchEngineUrl {
1109 base: "https://example.com/variant".to_string(),
1110 method: "GET".to_string(),
1111 params: vec![SearchUrlParam {
1112 name: "variant".to_string(),
1113 value: Some("test variant".to_string()),
1114 enterprise_value: None,
1115 experiment_config: None,
1116 }],
1117 search_term_param_name: Some("ship".to_string()),
1118 ..Default::default()
1119 },
1120 suggestions: Some(SearchEngineUrl {
1121 base: "https://example.com/suggestions-variant".to_string(),
1122 method: "GET".to_string(),
1123 params: vec![SearchUrlParam {
1124 name: "suggest-variant".to_string(),
1125 value: Some("sugg test variant".to_string()),
1126 enterprise_value: None,
1127 experiment_config: None,
1128 }],
1129 search_term_param_name: Some("variant".to_string()),
1130 ..Default::default()
1131 }),
1132 trending: Some(SearchEngineUrl {
1133 base: "https://example.com/trending-variant".to_string(),
1134 method: "GET".to_string(),
1135 params: vec![SearchUrlParam {
1136 name: "trend-variant".to_string(),
1137 value: Some("trend test variant".to_string()),
1138 enterprise_value: None,
1139 experiment_config: None,
1140 }],
1141 search_term_param_name: Some("trend".to_string()),
1142 exclude_partner_code_from_telemetry: true,
1143 ..Default::default()
1144 }),
1145 search_form: Some(SearchEngineUrl {
1146 base: "https://example.com/search_form".to_string(),
1147 method: "GET".to_string(),
1148 params: vec![SearchUrlParam {
1149 name: "search-form-name".to_string(),
1150 value: Some("search-form-value".to_string()),
1151 enterprise_value: None,
1152 experiment_config: None,
1153 }],
1154 ..Default::default()
1155 }),
1156 visual_search: Some(SearchEngineUrl {
1157 base: "https://example.com/visual-search-variant".to_string(),
1158 method: "GET".to_string(),
1159 params: vec![SearchUrlParam {
1160 name: "visual-search-variant-name".to_string(),
1161 value: Some("visual-search-variant-value".to_string()),
1162 enterprise_value: None,
1163 experiment_config: None,
1164 }],
1165 search_term_param_name: Some("url_variant".to_string()),
1166 display_name: Some("Visual Search Variant".to_string()),
1169 is_new_until: Some("2096-02-02".to_string()),
1170 exclude_partner_code_from_telemetry: false,
1171 }),
1172 },
1173 click_url: None
1174 }
1175 )
1176 }
1177
1178 #[test]
1179 fn test_from_configuration_details_merges_variants_locale_match() {
1180 let result = SearchEngineDefinition::from_configuration_details(
1181 &SearchUserEnvironment {
1182 locale: "en-GB".into(),
1184 ..Default::default()
1185 },
1186 "test",
1187 Lazy::force(&ENGINE_BASE).clone(),
1188 &ENGINE_VARIANT,
1189 &None,
1190 );
1191
1192 assert_eq!(
1193 result,
1194 SearchEngineDefinition {
1195 aliases: vec!["foo".to_string(), "bar".to_string()],
1196 charset: "ISO-8859-15".to_string(),
1197 classification: SearchEngineClassification::Unknown,
1198 identifier: "test".to_string(),
1199 is_new_until: Some("2063-04-05".to_string()),
1200 partner_code: "trek".to_string(),
1201 name: "Test".to_string(),
1202 optional: true,
1203 order_hint: None,
1204 telemetry_suffix: "star".to_string(),
1205 urls: SearchEngineUrls {
1206 search: SearchEngineUrl {
1207 base: "https://example.com/variant".to_string(),
1208 method: "GET".to_string(),
1209 params: vec![SearchUrlParam {
1210 name: "variant".to_string(),
1211 value: Some("test variant".to_string()),
1212 enterprise_value: None,
1213 experiment_config: None,
1214 }],
1215 search_term_param_name: Some("ship".to_string()),
1216 ..Default::default()
1217 },
1218 suggestions: Some(SearchEngineUrl {
1219 base: "https://example.com/suggestions-variant".to_string(),
1220 method: "GET".to_string(),
1221 params: vec![SearchUrlParam {
1222 name: "suggest-variant".to_string(),
1223 value: Some("sugg test variant".to_string()),
1224 enterprise_value: None,
1225 experiment_config: None,
1226 }],
1227 search_term_param_name: Some("variant".to_string()),
1228 ..Default::default()
1229 }),
1230 trending: Some(SearchEngineUrl {
1231 base: "https://example.com/trending-variant".to_string(),
1232 method: "GET".to_string(),
1233 params: vec![SearchUrlParam {
1234 name: "trend-variant".to_string(),
1235 value: Some("trend test variant".to_string()),
1236 enterprise_value: None,
1237 experiment_config: None,
1238 }],
1239 search_term_param_name: Some("trend".to_string()),
1240 exclude_partner_code_from_telemetry: true,
1241 ..Default::default()
1242 }),
1243 search_form: Some(SearchEngineUrl {
1244 base: "https://example.com/search_form".to_string(),
1245 method: "GET".to_string(),
1246 params: vec![SearchUrlParam {
1247 name: "search-form-name".to_string(),
1248 value: Some("search-form-value".to_string()),
1249 enterprise_value: None,
1250 experiment_config: None,
1251 }],
1252 ..Default::default()
1253 }),
1254 visual_search: Some(SearchEngineUrl {
1255 base: "https://example.com/visual-search-variant".to_string(),
1256 method: "GET".to_string(),
1257 params: vec![SearchUrlParam {
1258 name: "visual-search-variant-name".to_string(),
1259 value: Some("visual-search-variant-value".to_string()),
1260 enterprise_value: None,
1261 experiment_config: None,
1262 }],
1263 search_term_param_name: Some("url_variant".to_string()),
1264 display_name: Some("Visual Search Variant en-GB".to_string()),
1267 is_new_until: Some("2096-02-02".to_string()),
1268 exclude_partner_code_from_telemetry: false,
1269 }),
1270 },
1271 click_url: None
1272 }
1273 )
1274 }
1275
1276 static ENGINE_SUBVARIANT: Lazy<JSONEngineVariant> = Lazy::new(|| JSONEngineVariant {
1277 environment: JSONVariantEnvironment {
1278 all_regions_and_locales: true,
1279 ..Default::default()
1280 },
1281 is_new_until: Some("2063-04-05".to_string()),
1282 optional: true,
1283 partner_code: Some("trek2".to_string()),
1284 telemetry_suffix: Some("star2".to_string()),
1285 urls: Some(JSONEngineUrls {
1286 search: Some(JSONEngineUrl {
1287 base: Some("https://example.com/subvariant".to_string()),
1288 method: Some(JSONEngineMethod::Get),
1289 params: Some(vec![SearchUrlParam {
1290 name: "subvariant".to_string(),
1291 value: Some("test subvariant".to_string()),
1292 enterprise_value: None,
1293 experiment_config: None,
1294 }]),
1295 search_term_param_name: Some("shuttle".to_string()),
1296 ..Default::default()
1297 }),
1298 suggestions: Some(JSONEngineUrl {
1299 base: Some("https://example.com/suggestions-subvariant".to_string()),
1300 method: Some(JSONEngineMethod::Get),
1301 params: Some(vec![SearchUrlParam {
1302 name: "suggest-subvariant".to_string(),
1303 value: Some("sugg test subvariant".to_string()),
1304 enterprise_value: None,
1305 experiment_config: None,
1306 }]),
1307 search_term_param_name: Some("subvariant".to_string()),
1308 exclude_partner_code_from_telemetry: true,
1309 ..Default::default()
1310 }),
1311 trending: Some(JSONEngineUrl {
1312 base: Some("https://example.com/trending-subvariant".to_string()),
1313 method: Some(JSONEngineMethod::Get),
1314 params: Some(vec![SearchUrlParam {
1315 name: "trend-subvariant".to_string(),
1316 value: Some("trend test subvariant".to_string()),
1317 enterprise_value: None,
1318 experiment_config: None,
1319 }]),
1320 search_term_param_name: Some("subtrend".to_string()),
1321 ..Default::default()
1322 }),
1323 search_form: Some(JSONEngineUrl {
1324 base: Some("https://example.com/search-form-subvariant".to_string()),
1325 method: Some(crate::JSONEngineMethod::Get),
1326 params: Some(vec![SearchUrlParam {
1327 name: "search-form-subvariant".to_string(),
1328 value: Some("search form subvariant".to_string()),
1329 enterprise_value: None,
1330 experiment_config: None,
1331 }]),
1332 ..Default::default()
1333 }),
1334 visual_search: Some(JSONEngineUrl {
1335 base: Some("https://example.com/visual-search-subvariant".to_string()),
1336 method: Some(JSONEngineMethod::Get),
1337 params: Some(vec![SearchUrlParam {
1338 name: "visual-search-subvariant-name".to_string(),
1339 value: Some("visual-search-subvariant-value".to_string()),
1340 enterprise_value: None,
1341 experiment_config: None,
1342 }]),
1343 search_term_param_name: Some("url_subvariant".to_string()),
1344 display_name_map: Some(HashMap::from([
1345 (
1346 "default".to_string(),
1347 "Visual Search Subvariant".to_string(),
1348 ),
1349 (
1351 "en-GB".to_string(),
1352 "Visual Search Subvariant en-GB".to_string(),
1353 ),
1354 ])),
1355 is_new_until: Some("2097-03-03".to_string()),
1356 ..Default::default()
1357 }),
1358 }),
1359 sub_variants: vec![],
1360 });
1361
1362 #[test]
1363 fn test_from_configuration_details_merges_sub_variants() {
1364 let result = SearchEngineDefinition::from_configuration_details(
1365 &SearchUserEnvironment {
1366 locale: "fi".into(),
1367 ..Default::default()
1368 },
1369 "test",
1370 Lazy::force(&ENGINE_BASE).clone(),
1371 &ENGINE_VARIANT,
1372 &Some(ENGINE_SUBVARIANT.clone()),
1373 );
1374
1375 assert_eq!(
1376 result,
1377 SearchEngineDefinition {
1378 aliases: vec!["foo".to_string(), "bar".to_string()],
1379 charset: "ISO-8859-15".to_string(),
1380 classification: SearchEngineClassification::Unknown,
1381 identifier: "test".to_string(),
1382 is_new_until: Some("2063-04-05".to_string()),
1383 partner_code: "trek2".to_string(),
1384 name: "Test".to_string(),
1385 optional: true,
1386 order_hint: None,
1387 telemetry_suffix: "star2".to_string(),
1388 urls: SearchEngineUrls {
1389 search: SearchEngineUrl {
1390 base: "https://example.com/subvariant".to_string(),
1391 method: "GET".to_string(),
1392 params: vec![SearchUrlParam {
1393 name: "subvariant".to_string(),
1394 value: Some("test subvariant".to_string()),
1395 enterprise_value: None,
1396 experiment_config: None,
1397 }],
1398 search_term_param_name: Some("shuttle".to_string()),
1399 ..Default::default()
1400 },
1401 suggestions: Some(SearchEngineUrl {
1402 base: "https://example.com/suggestions-subvariant".to_string(),
1403 method: "GET".to_string(),
1404 params: vec![SearchUrlParam {
1405 name: "suggest-subvariant".to_string(),
1406 value: Some("sugg test subvariant".to_string()),
1407 enterprise_value: None,
1408 experiment_config: None,
1409 }],
1410 search_term_param_name: Some("subvariant".to_string()),
1411 exclude_partner_code_from_telemetry: true,
1412 ..Default::default()
1413 }),
1414 trending: Some(SearchEngineUrl {
1415 base: "https://example.com/trending-subvariant".to_string(),
1416 method: "GET".to_string(),
1417 params: vec![SearchUrlParam {
1418 name: "trend-subvariant".to_string(),
1419 value: Some("trend test subvariant".to_string()),
1420 enterprise_value: None,
1421 experiment_config: None,
1422 }],
1423 search_term_param_name: Some("subtrend".to_string()),
1424 ..Default::default()
1425 }),
1426 search_form: Some(SearchEngineUrl {
1427 base: "https://example.com/search-form-subvariant".to_string(),
1428 method: "GET".to_string(),
1429 params: vec![SearchUrlParam {
1430 name: "search-form-subvariant".to_string(),
1431 value: Some("search form subvariant".to_string()),
1432 enterprise_value: None,
1433 experiment_config: None,
1434 }],
1435 ..Default::default()
1436 }),
1437 visual_search: Some(SearchEngineUrl {
1438 base: "https://example.com/visual-search-subvariant".to_string(),
1439 method: "GET".to_string(),
1440 params: vec![SearchUrlParam {
1441 name: "visual-search-subvariant-name".to_string(),
1442 value: Some("visual-search-subvariant-value".to_string()),
1443 enterprise_value: None,
1444 experiment_config: None,
1445 }],
1446 search_term_param_name: Some("url_subvariant".to_string()),
1447 display_name: Some("Visual Search Subvariant".to_string()),
1450 is_new_until: Some("2097-03-03".to_string()),
1451 exclude_partner_code_from_telemetry: false,
1452 }),
1453 },
1454 click_url: None
1455 }
1456 )
1457 }
1458
1459 #[test]
1460 fn test_from_configuration_details_merges_sub_variants_locale_match() {
1461 let result = SearchEngineDefinition::from_configuration_details(
1462 &SearchUserEnvironment {
1463 locale: "en-GB".into(),
1465 ..Default::default()
1466 },
1467 "test",
1468 Lazy::force(&ENGINE_BASE).clone(),
1469 &ENGINE_VARIANT,
1470 &Some(ENGINE_SUBVARIANT.clone()),
1471 );
1472
1473 assert_eq!(
1474 result,
1475 SearchEngineDefinition {
1476 aliases: vec!["foo".to_string(), "bar".to_string()],
1477 charset: "ISO-8859-15".to_string(),
1478 classification: SearchEngineClassification::Unknown,
1479 identifier: "test".to_string(),
1480 is_new_until: Some("2063-04-05".to_string()),
1481 partner_code: "trek2".to_string(),
1482 name: "Test".to_string(),
1483 optional: true,
1484 order_hint: None,
1485 telemetry_suffix: "star2".to_string(),
1486 urls: SearchEngineUrls {
1487 search: SearchEngineUrl {
1488 base: "https://example.com/subvariant".to_string(),
1489 method: "GET".to_string(),
1490 params: vec![SearchUrlParam {
1491 name: "subvariant".to_string(),
1492 value: Some("test subvariant".to_string()),
1493 enterprise_value: None,
1494 experiment_config: None,
1495 }],
1496 search_term_param_name: Some("shuttle".to_string()),
1497 ..Default::default()
1498 },
1499 suggestions: Some(SearchEngineUrl {
1500 base: "https://example.com/suggestions-subvariant".to_string(),
1501 method: "GET".to_string(),
1502 params: vec![SearchUrlParam {
1503 name: "suggest-subvariant".to_string(),
1504 value: Some("sugg test subvariant".to_string()),
1505 enterprise_value: None,
1506 experiment_config: None,
1507 }],
1508 search_term_param_name: Some("subvariant".to_string()),
1509 exclude_partner_code_from_telemetry: true,
1510 ..Default::default()
1511 }),
1512 trending: Some(SearchEngineUrl {
1513 base: "https://example.com/trending-subvariant".to_string(),
1514 method: "GET".to_string(),
1515 params: vec![SearchUrlParam {
1516 name: "trend-subvariant".to_string(),
1517 value: Some("trend test subvariant".to_string()),
1518 enterprise_value: None,
1519 experiment_config: None,
1520 }],
1521 search_term_param_name: Some("subtrend".to_string()),
1522 ..Default::default()
1523 }),
1524 search_form: Some(SearchEngineUrl {
1525 base: "https://example.com/search-form-subvariant".to_string(),
1526 method: "GET".to_string(),
1527 params: vec![SearchUrlParam {
1528 name: "search-form-subvariant".to_string(),
1529 value: Some("search form subvariant".to_string()),
1530 enterprise_value: None,
1531 experiment_config: None,
1532 }],
1533 ..Default::default()
1534 }),
1535 visual_search: Some(SearchEngineUrl {
1536 base: "https://example.com/visual-search-subvariant".to_string(),
1537 method: "GET".to_string(),
1538 params: vec![SearchUrlParam {
1539 name: "visual-search-subvariant-name".to_string(),
1540 value: Some("visual-search-subvariant-value".to_string()),
1541 enterprise_value: None,
1542 experiment_config: None,
1543 }],
1544 search_term_param_name: Some("url_subvariant".to_string()),
1545 display_name: Some("Visual Search Subvariant en-GB".to_string()),
1548 is_new_until: Some("2097-03-03".to_string()),
1549 exclude_partner_code_from_telemetry: false,
1550 }),
1551 },
1552 click_url: None
1553 }
1554 )
1555 }
1556
1557 static ENGINES_LIST: Lazy<Vec<SearchEngineDefinition>> = Lazy::new(|| {
1558 vec![
1559 SearchEngineDefinition {
1560 identifier: "engine1".to_string(),
1561 name: "Test".to_string(),
1562 urls: SearchEngineUrls {
1563 search: SearchEngineUrl {
1564 base: "https://example.com".to_string(),
1565 ..Default::default()
1566 },
1567 ..Default::default()
1568 },
1569 ..Default::default()
1570 },
1571 SearchEngineDefinition {
1572 identifier: "engine2".to_string(),
1573 name: "Test 2".to_string(),
1574 urls: SearchEngineUrls {
1575 search: SearchEngineUrl {
1576 base: "https://example.com/2".to_string(),
1577 ..Default::default()
1578 },
1579 ..Default::default()
1580 },
1581 ..Default::default()
1582 },
1583 SearchEngineDefinition {
1584 identifier: "engine3".to_string(),
1585 name: "Test 3".to_string(),
1586 urls: SearchEngineUrls {
1587 search: SearchEngineUrl {
1588 base: "https://example.com/3".to_string(),
1589 ..Default::default()
1590 },
1591 ..Default::default()
1592 },
1593 ..Default::default()
1594 },
1595 SearchEngineDefinition {
1596 identifier: "engine4wildcardmatch".to_string(),
1597 name: "Test 4".to_string(),
1598 urls: SearchEngineUrls {
1599 search: SearchEngineUrl {
1600 base: "https://example.com/4".to_string(),
1601 ..Default::default()
1602 },
1603 ..Default::default()
1604 },
1605 ..Default::default()
1606 },
1607 ]
1608 });
1609
1610 #[test]
1611 fn test_determine_default_engines_returns_global_default() {
1612 let (default_engine_id, default_engine_private_id) = determine_default_engines(
1613 &ENGINES_LIST,
1614 Some(JSONDefaultEnginesRecord {
1615 global_default: "engine2".to_string(),
1616 global_default_private: String::new(),
1617 specific_defaults: Vec::new(),
1618 }),
1619 &SearchUserEnvironment {
1620 locale: "fi".into(),
1621 ..Default::default()
1622 },
1623 );
1624
1625 assert_eq!(
1626 default_engine_id.unwrap(),
1627 "engine2",
1628 "Should have returned the global default engine"
1629 );
1630 assert!(
1631 default_engine_private_id.is_none(),
1632 "Should not have returned an id for the private engine"
1633 );
1634
1635 let (default_engine_id, default_engine_private_id) = determine_default_engines(
1636 &ENGINES_LIST,
1637 Some(JSONDefaultEnginesRecord {
1638 global_default: "engine2".to_string(),
1639 global_default_private: String::new(),
1640 specific_defaults: vec![JSONSpecificDefaultRecord {
1641 default: "engine1".to_string(),
1642 default_private: String::new(),
1643 environment: JSONVariantEnvironment {
1644 locales: vec!["en-GB".to_string()],
1645 ..Default::default()
1646 },
1647 }],
1648 }),
1649 &SearchUserEnvironment {
1650 locale: "fi".into(),
1651 ..Default::default()
1652 },
1653 );
1654
1655 assert_eq!(
1656 default_engine_id.unwrap(),
1657 "engine2",
1658 "Should have returned the global default engine when no specific defaults environments match"
1659 );
1660 assert!(
1661 default_engine_private_id.is_none(),
1662 "Should not have returned an id for the private engine"
1663 );
1664
1665 let (default_engine_id, default_engine_private_id) = determine_default_engines(
1666 &ENGINES_LIST,
1667 Some(JSONDefaultEnginesRecord {
1668 global_default: "engine2".to_string(),
1669 global_default_private: String::new(),
1670 specific_defaults: vec![JSONSpecificDefaultRecord {
1671 default: "engine1".to_string(),
1672 default_private: String::new(),
1673 environment: JSONVariantEnvironment {
1674 locales: vec!["fi".to_string()],
1675 ..Default::default()
1676 },
1677 }],
1678 }),
1679 &SearchUserEnvironment {
1680 locale: "fi".into(),
1681 ..Default::default()
1682 },
1683 );
1684
1685 assert_eq!(
1686 default_engine_id.unwrap(),
1687 "engine1",
1688 "Should have returned the specific default when environments match"
1689 );
1690 assert!(
1691 default_engine_private_id.is_none(),
1692 "Should not have returned an id for the private engine"
1693 );
1694
1695 let (default_engine_id, default_engine_private_id) = determine_default_engines(
1696 &ENGINES_LIST,
1697 Some(JSONDefaultEnginesRecord {
1698 global_default: "engine2".to_string(),
1699 global_default_private: String::new(),
1700 specific_defaults: vec![JSONSpecificDefaultRecord {
1701 default: "engine4*".to_string(),
1702 default_private: String::new(),
1703 environment: JSONVariantEnvironment {
1704 locales: vec!["fi".to_string()],
1705 ..Default::default()
1706 },
1707 }],
1708 }),
1709 &SearchUserEnvironment {
1710 locale: "fi".into(),
1711 ..Default::default()
1712 },
1713 );
1714
1715 assert_eq!(
1716 default_engine_id.unwrap(),
1717 "engine4wildcardmatch",
1718 "Should have returned the specific default when using a wildcard match"
1719 );
1720 assert!(
1721 default_engine_private_id.is_none(),
1722 "Should not have returned an id for the private engine"
1723 );
1724
1725 let (default_engine_id, default_engine_private_id) = determine_default_engines(
1726 &ENGINES_LIST,
1727 Some(JSONDefaultEnginesRecord {
1728 global_default: "engine2".to_string(),
1729 global_default_private: String::new(),
1730 specific_defaults: vec![
1731 JSONSpecificDefaultRecord {
1732 default: "engine4*".to_string(),
1733 default_private: String::new(),
1734 environment: JSONVariantEnvironment {
1735 locales: vec!["fi".to_string()],
1736 ..Default::default()
1737 },
1738 },
1739 JSONSpecificDefaultRecord {
1740 default: "engine3".to_string(),
1741 default_private: String::new(),
1742 environment: JSONVariantEnvironment {
1743 locales: vec!["fi".to_string()],
1744 ..Default::default()
1745 },
1746 },
1747 ],
1748 }),
1749 &SearchUserEnvironment {
1750 locale: "fi".into(),
1751 ..Default::default()
1752 },
1753 );
1754
1755 assert_eq!(
1756 default_engine_id.unwrap(),
1757 "engine3",
1758 "Should have returned the last specific default when multiple environments match"
1759 );
1760 assert!(
1761 default_engine_private_id.is_none(),
1762 "Should not have returned an id for the private engine"
1763 );
1764 }
1765
1766 #[test]
1767 fn test_determine_default_engines_returns_global_default_private() {
1768 let (default_engine_id, default_engine_private_id) = determine_default_engines(
1769 &ENGINES_LIST,
1770 Some(JSONDefaultEnginesRecord {
1771 global_default: "engine2".to_string(),
1772 global_default_private: "engine3".to_string(),
1773 specific_defaults: Vec::new(),
1774 }),
1775 &SearchUserEnvironment {
1776 ..Default::default()
1777 },
1778 );
1779
1780 assert_eq!(
1781 default_engine_id.unwrap(),
1782 "engine2",
1783 "Should have returned the global default engine"
1784 );
1785 assert_eq!(
1786 default_engine_private_id.unwrap(),
1787 "engine3",
1788 "Should have returned the global default engine for private mode"
1789 );
1790
1791 let (default_engine_id, default_engine_private_id) = determine_default_engines(
1792 &ENGINES_LIST,
1793 Some(JSONDefaultEnginesRecord {
1794 global_default: "engine2".to_string(),
1795 global_default_private: "engine3".to_string(),
1796 specific_defaults: vec![JSONSpecificDefaultRecord {
1797 default: String::new(),
1798 default_private: "engine1".to_string(),
1799 environment: JSONVariantEnvironment {
1800 locales: vec!["en-GB".to_string()],
1801 ..Default::default()
1802 },
1803 }],
1804 }),
1805 &SearchUserEnvironment {
1806 locale: "fi".into(),
1807 ..Default::default()
1808 },
1809 );
1810
1811 assert_eq!(
1812 default_engine_id.unwrap(),
1813 "engine2",
1814 "Should have returned the global default engine when no specific defaults environments match"
1815 );
1816 assert_eq!(
1817 default_engine_private_id.unwrap(),
1818 "engine3",
1819 "Should have returned the global default engine for private mode when no specific defaults environments match"
1820 );
1821
1822 let (default_engine_id, default_engine_private_id) = determine_default_engines(
1823 &ENGINES_LIST,
1824 Some(JSONDefaultEnginesRecord {
1825 global_default: "engine2".to_string(),
1826 global_default_private: "engine3".to_string(),
1827 specific_defaults: vec![JSONSpecificDefaultRecord {
1828 default: String::new(),
1829 default_private: "engine1".to_string(),
1830 environment: JSONVariantEnvironment {
1831 locales: vec!["fi".to_string()],
1832 ..Default::default()
1833 },
1834 }],
1835 }),
1836 &SearchUserEnvironment {
1837 locale: "fi".into(),
1838 ..Default::default()
1839 },
1840 );
1841
1842 assert_eq!(
1843 default_engine_id.unwrap(),
1844 "engine2",
1845 "Should have returned the global default engine when specific environments match which override the private global default (and not the global default)."
1846 );
1847 assert_eq!(
1848 default_engine_private_id.unwrap(),
1849 "engine1",
1850 "Should have returned the specific default engine for private mode when environments match"
1851 );
1852
1853 let (default_engine_id, default_engine_private_id) = determine_default_engines(
1854 &ENGINES_LIST,
1855 Some(JSONDefaultEnginesRecord {
1856 global_default: "engine2".to_string(),
1857 global_default_private: String::new(),
1858 specific_defaults: vec![JSONSpecificDefaultRecord {
1859 default: String::new(),
1860 default_private: "engine4*".to_string(),
1861 environment: JSONVariantEnvironment {
1862 locales: vec!["fi".to_string()],
1863 ..Default::default()
1864 },
1865 }],
1866 }),
1867 &SearchUserEnvironment {
1868 locale: "fi".into(),
1869 ..Default::default()
1870 },
1871 );
1872
1873 assert_eq!(
1874 default_engine_id.unwrap(),
1875 "engine2",
1876 "Should have returned the global default engine when specific environments match which override the private global default (and not the global default)"
1877 );
1878 assert_eq!(
1879 default_engine_private_id.unwrap(),
1880 "engine4wildcardmatch",
1881 "Should have returned the specific default for private mode when using a wildcard match"
1882 );
1883 }
1884
1885 #[test]
1886 fn test_locale_matched_exactly() {
1887 let mut user_env = SearchUserEnvironment {
1888 locale: "en-CA".into(),
1889 ..Default::default()
1890 };
1891 negotiate_languages(&mut user_env, &["en-CA".to_string(), "fr".to_string()]);
1892 assert_eq!(
1893 user_env.locale, "en-CA",
1894 "Should return user locale unchanged if in available locales"
1895 );
1896 }
1897
1898 #[test]
1899 fn test_locale_fallback_to_base_locale() {
1900 let mut user_env = SearchUserEnvironment {
1901 locale: "de-AT".into(),
1902 ..Default::default()
1903 };
1904 negotiate_languages(&mut user_env, &["de".to_string()]);
1905 assert_eq!(
1906 user_env.locale, "de",
1907 "Should fallback to base locale if base is in available locales"
1908 );
1909 }
1910
1911 static ENGLISH_LOCALES: &[&str] = &["en-AU", "en-IE", "en-RU", "en-ZA"];
1912
1913 #[test]
1914 fn test_english_locales_fallbacks_to_en_us() {
1915 for user_locale in ENGLISH_LOCALES {
1916 let mut user_env = SearchUserEnvironment {
1917 locale: user_locale.to_string(),
1918 ..Default::default()
1919 };
1920 negotiate_languages(&mut user_env, &["en-US".to_string()]);
1921 assert_eq!(
1922 user_env.locale, "en-us",
1923 "Should remap {} to en-us when en-us is available",
1924 user_locale
1925 );
1926 }
1927 }
1928
1929 #[test]
1930 fn test_locale_unmatched() {
1931 let mut user_env = SearchUserEnvironment {
1932 locale: "fr-CA".into(),
1933 ..Default::default()
1934 };
1935 negotiate_languages(&mut user_env, &["de".to_string(), "en-US".to_string()]);
1936 assert_eq!(
1937 user_env.locale, "fr-CA",
1938 "Should leave locale unchanged if no match or english locale fallback is not found"
1939 );
1940 }
1941}