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