1use std::{
2 collections::{BTreeMap, HashMap, HashSet, VecDeque},
3 error::Error,
4 net::IpAddr,
5};
6
7use ferron_common::{
8 config::{Conditional, ErrorHandlerStatus},
9 modules::ModuleLoader,
10 observability::{ObservabilityBackendChannels, ObservabilityBackendLoader},
11};
12
13use super::{ServerConfiguration, ServerConfigurationFilters};
14
15pub fn merge_duplicates(mut server_configurations: Vec<ServerConfiguration>) -> Vec<ServerConfiguration> {
22 server_configurations.sort_by(|a, b| {
24 (
25 &a.filters.is_host,
26 &a.filters.port,
27 &a.filters.ip,
28 &a.filters.hostname,
29 &a.filters
30 .condition
31 .as_ref()
32 .map(|s| (&s.location_prefix, &s.conditionals)),
33 &a.filters.error_handler_status,
34 )
35 .cmp(&(
36 &b.filters.is_host,
37 &b.filters.port,
38 &b.filters.ip,
39 &b.filters.hostname,
40 &b.filters
41 .condition
42 .as_ref()
43 .map(|s| (&s.location_prefix, &s.conditionals)),
44 &b.filters.error_handler_status,
45 ))
46 });
47
48 let mut server_configurations = VecDeque::from(server_configurations);
50
51 let mut result = Vec::new();
52 while !server_configurations.is_empty() {
53 if let Some(mut current) = server_configurations.pop_front() {
54 while !server_configurations.is_empty()
56 && server_configurations[0].filters.is_host == current.filters.is_host
57 && server_configurations[0].filters.hostname == current.filters.hostname
58 && server_configurations[0].filters.ip == current.filters.ip
59 && server_configurations[0].filters.port == current.filters.port
60 && server_configurations[0].filters.condition == current.filters.condition
61 && server_configurations[0].filters.error_handler_status == current.filters.error_handler_status
62 {
63 if let Some(server_configuration) = server_configurations.pop_front() {
64 for (k, v) in server_configuration.entries {
66 current.entries.entry(k).or_default().inner.extend(v.inner);
67 }
68 }
69 }
70 result.push(current);
71 }
72 }
73
74 result
75}
76
77pub fn remove_and_add_global_configuration(
83 server_configurations: Vec<ServerConfiguration>,
84) -> Vec<ServerConfiguration> {
85 let mut new_server_configurations = Vec::new();
87 let mut has_global_non_host = false;
89
90 for server_configuration in server_configurations {
92 if !server_configuration.entries.is_empty() {
94 if server_configuration.filters.is_global_non_host() {
96 has_global_non_host = true;
97 }
98 new_server_configurations.push(server_configuration);
100 }
101 }
102
103 if !has_global_non_host {
105 new_server_configurations.insert(
106 0,
107 ServerConfiguration {
108 entries: HashMap::new(),
109 filters: ServerConfigurationFilters {
110 is_host: false,
111 hostname: None,
112 ip: None,
113 port: None,
114 condition: None,
115 error_handler_status: None,
116 },
117 modules: vec![],
118 observability: ObservabilityBackendChannels::new(),
119 },
120 );
121 }
122
123 new_server_configurations
125}
126
127#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)]
129enum ServerConfigurationFilter {
130 IsHost(bool),
132
133 Port(Option<u16>),
135
136 Ip(Option<IpAddr>),
138
139 Hostname(Option<String>),
141
142 Condition(Option<(String, Vec<Conditional>)>),
144
145 ErrorHandlerStatus(Option<ErrorHandlerStatus>),
147}
148
149struct ServerConfigurationFilterTrie {
151 children: BTreeMap<ServerConfigurationFilter, ServerConfigurationFilterTrie>,
152 index: Option<usize>,
153}
154
155impl ServerConfigurationFilterTrie {
156 pub fn new() -> Self {
158 Self {
159 children: BTreeMap::new(),
160 index: None,
161 }
162 }
163
164 pub fn insert(&mut self, filters: ServerConfigurationFilters, filters_index: usize) {
166 let no_host = !filters.is_host;
167 let no_port = filters.port.is_none();
168 let no_ip = filters.ip.is_none();
169 let no_hostname = filters.hostname.is_none();
170 let no_condition = filters.condition.is_none();
171 let no_error_handler_status = filters.error_handler_status.is_none();
172
173 let filter_vec = vec![
174 ServerConfigurationFilter::IsHost(filters.is_host),
175 ServerConfigurationFilter::Port(filters.port),
176 ServerConfigurationFilter::Ip(filters.ip),
177 ServerConfigurationFilter::Hostname(filters.hostname),
178 ServerConfigurationFilter::Condition(filters.condition.map(|s| (s.location_prefix, s.conditionals))),
179 ServerConfigurationFilter::ErrorHandlerStatus(filters.error_handler_status),
180 ];
181
182 let mut current_node = self;
183 for filter in filter_vec {
184 if match &filter {
185 ServerConfigurationFilter::IsHost(_) => {
186 no_host && no_port && no_ip && no_hostname && no_condition && no_error_handler_status
187 }
188 ServerConfigurationFilter::Port(_) => {
189 no_port && no_ip && no_hostname && no_condition && no_error_handler_status
190 }
191 ServerConfigurationFilter::Ip(_) => no_ip && no_hostname && no_condition && no_error_handler_status,
192 ServerConfigurationFilter::Hostname(_) => no_hostname && no_condition && no_error_handler_status,
193 ServerConfigurationFilter::Condition(_) => no_condition && no_error_handler_status,
194 ServerConfigurationFilter::ErrorHandlerStatus(_) => no_error_handler_status,
195 } && current_node.index.is_none()
196 {
197 current_node.index = Some(filters_index);
198 }
199 if !current_node.children.contains_key(&filter) {
200 current_node.children.insert(filter.clone(), Self::new());
201 }
202 match current_node.children.get_mut(&filter) {
203 Some(node) => current_node = node,
204 None => unreachable!(),
205 }
206 }
207 }
208
209 pub fn find_indices(&self, filters: ServerConfigurationFilters) -> Vec<usize> {
211 let filter_vec = vec![
212 ServerConfigurationFilter::IsHost(filters.is_host),
213 ServerConfigurationFilter::Port(filters.port),
214 ServerConfigurationFilter::Ip(filters.ip),
215 ServerConfigurationFilter::Hostname(filters.hostname),
216 ServerConfigurationFilter::Condition(filters.condition.map(|s| (s.location_prefix, s.conditionals))),
217 ServerConfigurationFilter::ErrorHandlerStatus(filters.error_handler_status),
218 ];
219
220 let mut current_node = self;
221 let mut indices = Vec::new();
222 for filter in filter_vec {
223 if indices.last() != current_node.index.as_ref() {
224 if let Some(index) = current_node.index {
225 indices.push(index);
226 }
227 }
228 let child = current_node.children.get(&filter);
229 match child {
230 Some(child) => {
231 current_node = child;
232 }
233 None => break,
234 }
235 }
236 indices.reverse();
237 indices
238 }
239}
240
241pub fn premerge_configuration(mut server_configurations: Vec<ServerConfiguration>) -> Vec<ServerConfiguration> {
248 server_configurations.sort_by(|a, b| a.filters.cmp(&b.filters));
250
251 let mut server_configuration_filter_trie = ServerConfigurationFilterTrie::new();
253 for (index, server_configuration) in server_configurations.iter().enumerate() {
254 server_configuration_filter_trie.insert(server_configuration.filters.clone(), index);
255 }
256
257 let mut new_server_configurations = Vec::with_capacity(server_configurations.len());
259
260 while let Some(mut server_configuration) = server_configurations.pop() {
262 let layers_indexes = server_configuration_filter_trie.find_indices(server_configuration.filters.clone());
264
265 let mut configuration_entries = server_configuration.entries;
267
268 for layer_index in layers_indexes {
270 if layer_index >= server_configurations.len() {
272 continue;
273 }
274
275 let mut properties_in_layer = HashSet::new();
277 let mut cloned_hashmap = server_configurations[layer_index].entries.clone();
279 let moved_hashmap_iterator = configuration_entries.into_iter();
281 for (property_name, mut property) in moved_hashmap_iterator {
283 match cloned_hashmap.get_mut(&property_name) {
284 Some(obtained_property) => {
285 if properties_in_layer.contains(&property_name) {
286 obtained_property.inner.append(&mut property.inner);
288 } else {
289 obtained_property.inner = property.inner;
291 }
292 }
293 None => {
294 cloned_hashmap.insert(property_name.clone(), property);
296 }
297 }
298 properties_in_layer.insert(property_name);
300 }
301 configuration_entries = cloned_hashmap;
303 }
304 server_configuration.entries = configuration_entries;
306
307 new_server_configurations.push(server_configuration);
309 }
310
311 new_server_configurations.reverse();
313 new_server_configurations
314}
315
316pub fn load_modules(
323 server_configurations: Vec<ServerConfiguration>,
324 server_modules: &mut [Box<dyn ModuleLoader + Send + Sync>],
325 server_observability_backends: &mut [Box<dyn ObservabilityBackendLoader + Send + Sync>],
326 secondary_runtime: &tokio::runtime::Runtime,
327) -> (
328 Vec<ServerConfiguration>,
329 Option<Box<dyn Error + Send + Sync>>,
330 Vec<String>,
331) {
332 let mut new_server_configurations = Vec::new();
334 let mut first_server_module_error = None;
336 let mut unused_properties = HashSet::new();
338
339 let global_configuration = find_global_configuration(&server_configurations);
341
342 for mut server_configuration in server_configurations {
344 let mut used_properties = HashSet::new();
346
347 for server_observability_backend in server_observability_backends.iter_mut() {
349 let requirements = server_observability_backend.get_requirements();
351 let mut requirements_met = true;
353 for requirement in requirements {
354 requirements_met = false;
355 if server_configuration
357 .entries
358 .get(requirement)
359 .and_then(|e| e.get_value())
360 .is_some_and(|v| !v.is_null() && v.as_bool().unwrap_or(true))
361 {
362 requirements_met = true;
363 break;
364 }
365 }
366 if let Err(error) =
368 server_observability_backend.validate_configuration(&server_configuration, &mut used_properties)
369 {
370 if first_server_module_error.is_none() {
372 first_server_module_error
373 .replace(anyhow::anyhow!("{error} (at {})", server_configuration.filters).into_boxed_dyn_error());
374 }
375 break;
377 }
378 if requirements_met {
380 match server_observability_backend.load_observability_backend(
382 &server_configuration,
383 global_configuration.as_ref(),
384 secondary_runtime,
385 ) {
386 Ok(loaded_observability_backend) => {
387 if let Some(channel) = loaded_observability_backend.get_log_channel() {
388 server_configuration.observability.add_log_channel(channel);
389 }
390 if let Some(channel) = loaded_observability_backend.get_metric_channel() {
391 server_configuration.observability.add_metric_channel(channel);
392 }
393 if let Some(channel) = loaded_observability_backend.get_trace_channel() {
394 server_configuration.observability.add_trace_channel(channel);
395 }
396 }
397 Err(error) => {
398 if first_server_module_error.is_none() {
400 first_server_module_error
401 .replace(anyhow::anyhow!("{error} (at {})", server_configuration.filters).into_boxed_dyn_error());
402 }
403 break;
405 }
406 }
407 }
408 }
409
410 if first_server_module_error.is_none() {
411 for server_module in server_modules.iter_mut() {
413 let requirements = server_module.get_requirements();
415 let mut requirements_met = true;
417 for requirement in requirements {
418 requirements_met = false;
419 if server_configuration
421 .entries
422 .get(requirement)
423 .and_then(|e| e.get_value())
424 .is_some_and(|v| !v.is_null() && v.as_bool().unwrap_or(true))
425 {
426 requirements_met = true;
427 break;
428 }
429 }
430 if let Err(error) = server_module.validate_configuration(&server_configuration, &mut used_properties) {
432 if first_server_module_error.is_none() {
434 first_server_module_error
435 .replace(anyhow::anyhow!("{error} (at {})", server_configuration.filters).into_boxed_dyn_error());
436 }
437 break;
439 }
440 if requirements_met {
442 match server_module.load_module(&server_configuration, global_configuration.as_ref(), secondary_runtime) {
444 Ok(loaded_module) => server_configuration.modules.push(loaded_module),
445 Err(error) => {
446 if first_server_module_error.is_none() {
448 first_server_module_error
449 .replace(anyhow::anyhow!("{error} (at {})", server_configuration.filters).into_boxed_dyn_error());
450 }
451 break;
453 }
454 }
455 }
456 }
457 }
458
459 for property in server_configuration.entries.keys() {
461 if !property.starts_with("UNDOCUMENTED_") && !used_properties.contains(property) {
462 unused_properties.insert(property.to_string());
463 }
464 }
465
466 new_server_configurations.push(server_configuration);
468 }
469 (
474 new_server_configurations,
475 first_server_module_error,
476 unused_properties.into_iter().collect(),
477 )
478}
479
480fn find_global_configuration(server_configurations: &[ServerConfiguration]) -> Option<ServerConfiguration> {
482 let mut iterator = server_configurations.iter();
484 let first_found = iterator.find(|server_configuration| {
485 server_configuration.filters.is_global() || server_configuration.filters.is_global_non_host()
486 });
487 if let Some(first_found) = first_found {
488 if first_found.filters.is_global() {
489 return Some(first_found.clone());
490 }
491 for server_configuration in iterator {
492 if server_configuration.filters.is_global() {
493 return Some(server_configuration.clone());
494 } else if !server_configuration.filters.is_global_non_host() {
495 return Some(first_found.clone());
496 }
497 }
498 }
499 None
500}
501
502#[cfg(test)]
503mod tests {
504 use crate::config::*;
505
506 use super::*;
507 use std::collections::HashMap;
508 use std::net::{IpAddr, Ipv4Addr};
509
510 fn make_filters(
511 is_host: bool,
512 hostname: Option<&str>,
513 ip: Option<IpAddr>,
514 port: Option<u16>,
515 location_prefix: Option<&str>,
516 error_handler_status: Option<ErrorHandlerStatus>,
517 ) -> ServerConfigurationFilters {
518 ServerConfigurationFilters {
519 is_host,
520 hostname: hostname.map(String::from),
521 ip,
522 port,
523 condition: location_prefix.map(|prefix| Conditions {
524 location_prefix: prefix.to_string(),
525 conditionals: vec![],
526 }),
527 error_handler_status,
528 }
529 }
530
531 fn make_entry(values: Vec<ServerConfigurationValue>) -> ServerConfigurationEntries {
532 ServerConfigurationEntries {
533 inner: vec![ServerConfigurationEntry {
534 values,
535 props: HashMap::new(),
536 }],
537 }
538 }
539
540 fn make_entry_premerge(key: &str, value: ServerConfigurationValue) -> (String, ServerConfigurationEntries) {
541 let entry = ServerConfigurationEntry {
542 values: vec![value],
543 props: HashMap::new(),
544 };
545 (key.to_string(), ServerConfigurationEntries { inner: vec![entry] })
546 }
547
548 fn config_with_filters(
549 is_host: bool,
550 hostname: Option<&str>,
551 ip: Option<IpAddr>,
552 port: Option<u16>,
553 location_prefix: Option<&str>,
554 error_handler_status: Option<ErrorHandlerStatus>,
555 entries: Vec<(String, ServerConfigurationEntries)>,
556 ) -> ServerConfiguration {
557 ServerConfiguration {
558 filters: ServerConfigurationFilters {
559 is_host,
560 hostname: hostname.map(|s| s.to_string()),
561 ip,
562 port,
563 condition: location_prefix.map(|prefix| Conditions {
564 location_prefix: prefix.to_string(),
565 conditionals: vec![],
566 }),
567 error_handler_status,
568 },
569 entries: entries.into_iter().collect(),
570 modules: vec![],
571 observability: ObservabilityBackendChannels::new(),
572 }
573 }
574
575 #[test]
576 fn merges_identical_filters_and_combines_entries() {
577 let filters = make_filters(
578 true,
579 Some("example.com"),
580 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
581 Some(8080),
582 Some("/api"),
583 Some(ErrorHandlerStatus::Status(404)),
584 );
585
586 let filters_2 = make_filters(
587 true,
588 Some("example.com"),
589 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
590 Some(8080),
591 Some("/api"),
592 Some(ErrorHandlerStatus::Status(404)),
593 );
594
595 let mut config1_entries = HashMap::new();
596 config1_entries.insert(
597 "route".to_string(),
598 make_entry(vec![ServerConfigurationValue::String("v1".to_string())]),
599 );
600
601 let mut config2_entries = HashMap::new();
602 config2_entries.insert(
603 "route".to_string(),
604 make_entry(vec![ServerConfigurationValue::String("v2".to_string())]),
605 );
606
607 let config1 = ServerConfiguration {
608 filters: filters_2,
609 entries: config1_entries,
610 modules: vec![],
611 observability: ObservabilityBackendChannels::new(),
612 };
613
614 let config2 = ServerConfiguration {
615 filters,
616 entries: config2_entries,
617 modules: vec![],
618 observability: ObservabilityBackendChannels::new(),
619 };
620
621 let merged = merge_duplicates(vec![config1, config2]);
622 assert_eq!(merged.len(), 1);
623
624 let merged_entries = &merged[0].entries;
625 assert!(merged_entries.contains_key("route"));
626 let route_entry = merged_entries.get("route").unwrap();
627 let values: Vec<_> = route_entry.inner.iter().flat_map(|e| e.values.iter()).collect();
628 assert_eq!(values.len(), 2);
629 assert!(values.contains(&&ServerConfigurationValue::String("v1".into())));
630 assert!(values.contains(&&ServerConfigurationValue::String("v2".into())));
631 }
632
633 #[test]
634 fn does_not_merge_different_filters() {
635 let filters1 = make_filters(
636 true,
637 Some("example.com"),
638 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
639 Some(8080),
640 Some("/api"),
641 None,
642 );
643
644 let filters2 = make_filters(
645 true,
646 Some("example.org"),
647 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
648 Some(8080),
649 Some("/api"),
650 None,
651 );
652
653 let mut config1_entries = HashMap::new();
654 config1_entries.insert(
655 "route".to_string(),
656 make_entry(vec![ServerConfigurationValue::String("v1".to_string())]),
657 );
658
659 let mut config2_entries = HashMap::new();
660 config2_entries.insert(
661 "route".to_string(),
662 make_entry(vec![ServerConfigurationValue::String("v2".to_string())]),
663 );
664
665 let config1 = ServerConfiguration {
666 filters: filters1,
667 entries: config1_entries,
668 modules: vec![],
669 observability: ObservabilityBackendChannels::new(),
670 };
671
672 let config2 = ServerConfiguration {
673 filters: filters2,
674 entries: config2_entries,
675 modules: vec![],
676 observability: ObservabilityBackendChannels::new(),
677 };
678
679 let merged = merge_duplicates(vec![config1, config2]);
680 assert_eq!(merged.len(), 2);
681 }
682
683 #[test]
684 fn handles_filters_then_unique_then_duplicate() {
685 let filters1 = make_filters(
686 true,
687 Some("example.com"),
688 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
689 Some(8080),
690 Some("/api"),
691 None,
692 );
693
694 let filters2 = make_filters(
695 true,
696 Some("example.org"),
697 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
698 Some(8080),
699 Some("/api"),
700 None,
701 );
702
703 let mut config1_entries = HashMap::new();
704 config1_entries.insert(
705 "route".to_string(),
706 make_entry(vec![ServerConfigurationValue::String("v1".to_string())]),
707 );
708
709 let mut config2_entries = HashMap::new();
710 config2_entries.insert(
711 "route".to_string(),
712 make_entry(vec![ServerConfigurationValue::String("v2".to_string())]),
713 );
714
715 let mut config3_entries = HashMap::new();
716 config3_entries.insert(
717 "route".to_string(),
718 make_entry(vec![ServerConfigurationValue::String("v3".to_string())]),
719 );
720
721 let config1 = ServerConfiguration {
722 filters: filters1.clone(),
723 entries: config1_entries,
724 modules: vec![],
725 observability: ObservabilityBackendChannels::new(),
726 };
727
728 let config2 = ServerConfiguration {
729 filters: filters2,
730 entries: config2_entries,
731 modules: vec![],
732 observability: ObservabilityBackendChannels::new(),
733 };
734
735 let config3 = ServerConfiguration {
736 filters: filters1,
737 entries: config3_entries,
738 modules: vec![],
739 observability: ObservabilityBackendChannels::new(),
740 };
741
742 let merged = merge_duplicates(vec![config1, config2, config3]);
743 assert_eq!(merged.len(), 2);
744 }
745
746 #[test]
747 fn merges_entries_with_non_overlapping_keys() {
748 let filters = make_filters(
749 true,
750 Some("example.com"),
751 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
752 Some(8080),
753 None,
754 None,
755 );
756
757 let filters_2 = make_filters(
758 true,
759 Some("example.com"),
760 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
761 Some(8080),
762 None,
763 None,
764 );
765
766 let mut config1_entries = HashMap::new();
767 config1_entries.insert(
768 "route1".to_string(),
769 make_entry(vec![ServerConfigurationValue::String("r1".to_string())]),
770 );
771
772 let mut config2_entries = HashMap::new();
773 config2_entries.insert(
774 "route2".to_string(),
775 make_entry(vec![ServerConfigurationValue::String("r2".to_string())]),
776 );
777
778 let config1 = ServerConfiguration {
779 filters: filters_2,
780 entries: config1_entries,
781 modules: vec![],
782 observability: ObservabilityBackendChannels::new(),
783 };
784
785 let config2 = ServerConfiguration {
786 filters,
787 entries: config2_entries,
788 modules: vec![],
789 observability: ObservabilityBackendChannels::new(),
790 };
791
792 let merged = merge_duplicates(vec![config1, config2]);
793 assert_eq!(merged.len(), 1);
794
795 let merged_entries = &merged[0].entries;
796 assert_eq!(merged_entries.len(), 2);
797 assert!(merged_entries.contains_key("route1"));
798 assert!(merged_entries.contains_key("route2"));
799 }
800
801 #[test]
802 fn test_no_merge_returns_all() {
803 let config1 = config_with_filters(
804 true,
805 Some("example.com"),
806 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
807 Some(80),
808 None,
809 None,
810 vec![make_entry_premerge(
811 "key1",
812 ServerConfigurationValue::String("val1".into()),
813 )],
814 );
815
816 let config2 = config_with_filters(
817 true,
818 Some("example.org"),
819 Some(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))),
820 Some(8080),
821 None,
822 None,
823 vec![make_entry_premerge(
824 "key2",
825 ServerConfigurationValue::String("val2".into()),
826 )],
827 );
828
829 let merged = premerge_configuration(vec![config1, config2]);
830
831 assert_eq!(merged.len(), 2);
832 assert!(merged.iter().any(|c| c.entries.contains_key("key1")));
833 assert!(merged.iter().any(|c| c.entries.contains_key("key2")));
834 }
835
836 #[test]
837 fn test_merge_case6_is_host() {
838 let base = config_with_filters(
840 false,
841 None,
842 None,
843 None,
844 None,
845 None,
846 vec![make_entry_premerge(
847 "shared",
848 ServerConfigurationValue::String("base".into()),
849 )],
850 );
851
852 let specific = config_with_filters(
854 true,
855 None,
856 None,
857 None,
858 None,
859 None,
860 vec![make_entry_premerge(
861 "shared",
862 ServerConfigurationValue::String("specific".into()),
863 )],
864 );
865
866 let merged = premerge_configuration(vec![base, specific]);
867 assert_eq!(merged.len(), 2);
868
869 let entries = &merged[1].entries["shared"].inner;
870 assert_eq!(entries.len(), 1);
871 assert_eq!(entries[0].values[0].as_str(), Some("specific"));
872 }
873
874 #[test]
875 fn test_merge_case5_port() {
876 let base = config_with_filters(
878 true,
879 None,
880 None,
881 None,
882 None,
883 None,
884 vec![make_entry_premerge(
885 "shared",
886 ServerConfigurationValue::String("base".into()),
887 )],
888 );
889
890 let specific = config_with_filters(
892 true,
893 None,
894 None,
895 Some(80),
896 None,
897 None,
898 vec![make_entry_premerge(
899 "shared",
900 ServerConfigurationValue::String("specific".into()),
901 )],
902 );
903
904 let merged = premerge_configuration(vec![base, specific]);
905 assert_eq!(merged.len(), 2);
906
907 let entries = &merged[1].entries["shared"].inner;
908 assert_eq!(entries.len(), 1);
909 assert_eq!(entries[0].values[0].as_str(), Some("specific"));
910 }
911
912 #[test]
913 fn test_merge_case1_error_handler() {
914 let base = config_with_filters(
915 true,
916 Some("host"),
917 Some(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))),
918 Some(3000),
919 Some("/api"),
920 None,
921 vec![make_entry_premerge(
922 "eh",
923 ServerConfigurationValue::String("base".into()),
924 )],
925 );
926
927 let specific = config_with_filters(
928 true,
929 Some("host"),
930 Some(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))),
931 Some(3000),
932 Some("/api"),
933 Some(ErrorHandlerStatus::Any),
934 vec![make_entry_premerge(
935 "eh",
936 ServerConfigurationValue::String("specific".into()),
937 )],
938 );
939
940 let merged = premerge_configuration(vec![base, specific]);
941 assert_eq!(merged.len(), 2);
942
943 let entries = &merged[1].entries["eh"].inner;
944 assert_eq!(entries.len(), 1);
945 assert_eq!(entries[0].values[0].as_str(), Some("specific"));
946 }
947
948 #[test]
949 fn test_merge_preserves_specificity_order() {
950 let configs = vec![
951 config_with_filters(
952 true,
953 None,
954 None,
955 None,
956 None,
957 None,
958 vec![make_entry_premerge("a", ServerConfigurationValue::String("v1".into()))],
959 ),
960 config_with_filters(
961 true,
962 None,
963 None,
964 Some(80),
965 None,
966 None,
967 vec![make_entry_premerge("a", ServerConfigurationValue::String("v2".into()))],
968 ),
969 config_with_filters(
970 true,
971 Some("host"),
972 None,
973 Some(80),
974 None,
975 None,
976 vec![make_entry_premerge("a", ServerConfigurationValue::String("v3".into()))],
977 ),
978 ];
979
980 let merged = premerge_configuration(configs);
981 assert_eq!(merged.len(), 3);
982
983 let entries = &merged[2].entries["a"].inner;
984 assert_eq!(entries.len(), 1);
985 assert_eq!(entries[0].values[0].as_str(), Some("v3"));
986 }
987}