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 match server_observability_backend.validate_configuration(&server_configuration, &mut used_properties) {
368 Ok(_) => (),
369 Err(error) => {
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 }
379 if requirements_met {
381 match server_observability_backend.load_observability_backend(
383 &server_configuration,
384 global_configuration.as_ref(),
385 secondary_runtime,
386 ) {
387 Ok(loaded_observability_backend) => {
388 if let Some(channel) = loaded_observability_backend.get_log_channel() {
389 server_configuration.observability.add_log_channel(channel);
390 }
391 if let Some(channel) = loaded_observability_backend.get_metric_channel() {
392 server_configuration.observability.add_metric_channel(channel);
393 }
394 if let Some(channel) = loaded_observability_backend.get_trace_channel() {
395 server_configuration.observability.add_trace_channel(channel);
396 }
397 }
398 Err(error) => {
399 if first_server_module_error.is_none() {
401 first_server_module_error
402 .replace(anyhow::anyhow!("{error} (at {})", server_configuration.filters).into_boxed_dyn_error());
403 }
404 break;
406 }
407 }
408 }
409 }
410
411 if first_server_module_error.is_none() {
412 for server_module in server_modules.iter_mut() {
414 let requirements = server_module.get_requirements();
416 let mut requirements_met = true;
418 for requirement in requirements {
419 requirements_met = false;
420 if server_configuration
422 .entries
423 .get(requirement)
424 .and_then(|e| e.get_value())
425 .is_some_and(|v| !v.is_null() && v.as_bool().unwrap_or(true))
426 {
427 requirements_met = true;
428 break;
429 }
430 }
431 match server_module.validate_configuration(&server_configuration, &mut used_properties) {
433 Ok(_) => (),
434 Err(error) => {
435 if first_server_module_error.is_none() {
437 first_server_module_error
438 .replace(anyhow::anyhow!("{error} (at {})", server_configuration.filters).into_boxed_dyn_error());
439 }
440 break;
442 }
443 }
444 if requirements_met {
446 match server_module.load_module(&server_configuration, global_configuration.as_ref(), secondary_runtime) {
448 Ok(loaded_module) => server_configuration.modules.push(loaded_module),
449 Err(error) => {
450 if first_server_module_error.is_none() {
452 first_server_module_error
453 .replace(anyhow::anyhow!("{error} (at {})", server_configuration.filters).into_boxed_dyn_error());
454 }
455 break;
457 }
458 }
459 }
460 }
461 }
462
463 for property in server_configuration.entries.keys() {
465 if !property.starts_with("UNDOCUMENTED_") && !used_properties.contains(property) {
466 unused_properties.insert(property.to_string());
467 }
468 }
469
470 new_server_configurations.push(server_configuration);
472 }
473 (
478 new_server_configurations,
479 first_server_module_error,
480 unused_properties.into_iter().collect(),
481 )
482}
483
484fn find_global_configuration(server_configurations: &[ServerConfiguration]) -> Option<ServerConfiguration> {
486 let mut iterator = server_configurations.iter();
488 let first_found = iterator.find(|server_configuration| {
489 server_configuration.filters.is_global() || server_configuration.filters.is_global_non_host()
490 });
491 if let Some(first_found) = first_found {
492 if first_found.filters.is_global() {
493 return Some(first_found.clone());
494 }
495 for server_configuration in iterator {
496 if server_configuration.filters.is_global() {
497 return Some(server_configuration.clone());
498 } else if !server_configuration.filters.is_global_non_host() {
499 return Some(first_found.clone());
500 }
501 }
502 }
503 None
504}
505
506#[cfg(test)]
507mod tests {
508 use crate::config::*;
509
510 use super::*;
511 use std::collections::HashMap;
512 use std::net::{IpAddr, Ipv4Addr};
513
514 fn make_filters(
515 is_host: bool,
516 hostname: Option<&str>,
517 ip: Option<IpAddr>,
518 port: Option<u16>,
519 location_prefix: Option<&str>,
520 error_handler_status: Option<ErrorHandlerStatus>,
521 ) -> ServerConfigurationFilters {
522 ServerConfigurationFilters {
523 is_host,
524 hostname: hostname.map(String::from),
525 ip,
526 port,
527 condition: location_prefix.map(|prefix| Conditions {
528 location_prefix: prefix.to_string(),
529 conditionals: vec![],
530 }),
531 error_handler_status,
532 }
533 }
534
535 fn make_entry(values: Vec<ServerConfigurationValue>) -> ServerConfigurationEntries {
536 ServerConfigurationEntries {
537 inner: vec![ServerConfigurationEntry {
538 values,
539 props: HashMap::new(),
540 }],
541 }
542 }
543
544 fn make_entry_premerge(key: &str, value: ServerConfigurationValue) -> (String, ServerConfigurationEntries) {
545 let entry = ServerConfigurationEntry {
546 values: vec![value],
547 props: HashMap::new(),
548 };
549 (key.to_string(), ServerConfigurationEntries { inner: vec![entry] })
550 }
551
552 fn config_with_filters(
553 is_host: bool,
554 hostname: Option<&str>,
555 ip: Option<IpAddr>,
556 port: Option<u16>,
557 location_prefix: Option<&str>,
558 error_handler_status: Option<ErrorHandlerStatus>,
559 entries: Vec<(String, ServerConfigurationEntries)>,
560 ) -> ServerConfiguration {
561 ServerConfiguration {
562 filters: ServerConfigurationFilters {
563 is_host,
564 hostname: hostname.map(|s| s.to_string()),
565 ip,
566 port,
567 condition: location_prefix.map(|prefix| Conditions {
568 location_prefix: prefix.to_string(),
569 conditionals: vec![],
570 }),
571 error_handler_status,
572 },
573 entries: entries.into_iter().collect(),
574 modules: vec![],
575 observability: ObservabilityBackendChannels::new(),
576 }
577 }
578
579 #[test]
580 fn merges_identical_filters_and_combines_entries() {
581 let filters = make_filters(
582 true,
583 Some("example.com"),
584 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
585 Some(8080),
586 Some("/api"),
587 Some(ErrorHandlerStatus::Status(404)),
588 );
589
590 let filters_2 = make_filters(
591 true,
592 Some("example.com"),
593 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
594 Some(8080),
595 Some("/api"),
596 Some(ErrorHandlerStatus::Status(404)),
597 );
598
599 let mut config1_entries = HashMap::new();
600 config1_entries.insert(
601 "route".to_string(),
602 make_entry(vec![ServerConfigurationValue::String("v1".to_string())]),
603 );
604
605 let mut config2_entries = HashMap::new();
606 config2_entries.insert(
607 "route".to_string(),
608 make_entry(vec![ServerConfigurationValue::String("v2".to_string())]),
609 );
610
611 let config1 = ServerConfiguration {
612 filters: filters_2,
613 entries: config1_entries,
614 modules: vec![],
615 observability: ObservabilityBackendChannels::new(),
616 };
617
618 let config2 = ServerConfiguration {
619 filters,
620 entries: config2_entries,
621 modules: vec![],
622 observability: ObservabilityBackendChannels::new(),
623 };
624
625 let merged = merge_duplicates(vec![config1, config2]);
626 assert_eq!(merged.len(), 1);
627
628 let merged_entries = &merged[0].entries;
629 assert!(merged_entries.contains_key("route"));
630 let route_entry = merged_entries.get("route").unwrap();
631 let values: Vec<_> = route_entry.inner.iter().flat_map(|e| e.values.iter()).collect();
632 assert_eq!(values.len(), 2);
633 assert!(values.contains(&&ServerConfigurationValue::String("v1".into())));
634 assert!(values.contains(&&ServerConfigurationValue::String("v2".into())));
635 }
636
637 #[test]
638 fn does_not_merge_different_filters() {
639 let filters1 = make_filters(
640 true,
641 Some("example.com"),
642 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
643 Some(8080),
644 Some("/api"),
645 None,
646 );
647
648 let filters2 = make_filters(
649 true,
650 Some("example.org"),
651 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
652 Some(8080),
653 Some("/api"),
654 None,
655 );
656
657 let mut config1_entries = HashMap::new();
658 config1_entries.insert(
659 "route".to_string(),
660 make_entry(vec![ServerConfigurationValue::String("v1".to_string())]),
661 );
662
663 let mut config2_entries = HashMap::new();
664 config2_entries.insert(
665 "route".to_string(),
666 make_entry(vec![ServerConfigurationValue::String("v2".to_string())]),
667 );
668
669 let config1 = ServerConfiguration {
670 filters: filters1,
671 entries: config1_entries,
672 modules: vec![],
673 observability: ObservabilityBackendChannels::new(),
674 };
675
676 let config2 = ServerConfiguration {
677 filters: filters2,
678 entries: config2_entries,
679 modules: vec![],
680 observability: ObservabilityBackendChannels::new(),
681 };
682
683 let merged = merge_duplicates(vec![config1, config2]);
684 assert_eq!(merged.len(), 2);
685 }
686
687 #[test]
688 fn handles_filters_then_unique_then_duplicate() {
689 let filters1 = make_filters(
690 true,
691 Some("example.com"),
692 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
693 Some(8080),
694 Some("/api"),
695 None,
696 );
697
698 let filters2 = make_filters(
699 true,
700 Some("example.org"),
701 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
702 Some(8080),
703 Some("/api"),
704 None,
705 );
706
707 let mut config1_entries = HashMap::new();
708 config1_entries.insert(
709 "route".to_string(),
710 make_entry(vec![ServerConfigurationValue::String("v1".to_string())]),
711 );
712
713 let mut config2_entries = HashMap::new();
714 config2_entries.insert(
715 "route".to_string(),
716 make_entry(vec![ServerConfigurationValue::String("v2".to_string())]),
717 );
718
719 let mut config3_entries = HashMap::new();
720 config3_entries.insert(
721 "route".to_string(),
722 make_entry(vec![ServerConfigurationValue::String("v3".to_string())]),
723 );
724
725 let config1 = ServerConfiguration {
726 filters: filters1.clone(),
727 entries: config1_entries,
728 modules: vec![],
729 observability: ObservabilityBackendChannels::new(),
730 };
731
732 let config2 = ServerConfiguration {
733 filters: filters2,
734 entries: config2_entries,
735 modules: vec![],
736 observability: ObservabilityBackendChannels::new(),
737 };
738
739 let config3 = ServerConfiguration {
740 filters: filters1,
741 entries: config3_entries,
742 modules: vec![],
743 observability: ObservabilityBackendChannels::new(),
744 };
745
746 let merged = merge_duplicates(vec![config1, config2, config3]);
747 assert_eq!(merged.len(), 2);
748 }
749
750 #[test]
751 fn merges_entries_with_non_overlapping_keys() {
752 let filters = make_filters(
753 true,
754 Some("example.com"),
755 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
756 Some(8080),
757 None,
758 None,
759 );
760
761 let filters_2 = make_filters(
762 true,
763 Some("example.com"),
764 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
765 Some(8080),
766 None,
767 None,
768 );
769
770 let mut config1_entries = HashMap::new();
771 config1_entries.insert(
772 "route1".to_string(),
773 make_entry(vec![ServerConfigurationValue::String("r1".to_string())]),
774 );
775
776 let mut config2_entries = HashMap::new();
777 config2_entries.insert(
778 "route2".to_string(),
779 make_entry(vec![ServerConfigurationValue::String("r2".to_string())]),
780 );
781
782 let config1 = ServerConfiguration {
783 filters: filters_2,
784 entries: config1_entries,
785 modules: vec![],
786 observability: ObservabilityBackendChannels::new(),
787 };
788
789 let config2 = ServerConfiguration {
790 filters,
791 entries: config2_entries,
792 modules: vec![],
793 observability: ObservabilityBackendChannels::new(),
794 };
795
796 let merged = merge_duplicates(vec![config1, config2]);
797 assert_eq!(merged.len(), 1);
798
799 let merged_entries = &merged[0].entries;
800 assert_eq!(merged_entries.len(), 2);
801 assert!(merged_entries.contains_key("route1"));
802 assert!(merged_entries.contains_key("route2"));
803 }
804
805 #[test]
806 fn test_no_merge_returns_all() {
807 let config1 = config_with_filters(
808 true,
809 Some("example.com"),
810 Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))),
811 Some(80),
812 None,
813 None,
814 vec![make_entry_premerge(
815 "key1",
816 ServerConfigurationValue::String("val1".into()),
817 )],
818 );
819
820 let config2 = config_with_filters(
821 true,
822 Some("example.org"),
823 Some(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1))),
824 Some(8080),
825 None,
826 None,
827 vec![make_entry_premerge(
828 "key2",
829 ServerConfigurationValue::String("val2".into()),
830 )],
831 );
832
833 let merged = premerge_configuration(vec![config1, config2]);
834
835 assert_eq!(merged.len(), 2);
836 assert!(merged.iter().any(|c| c.entries.contains_key("key1")));
837 assert!(merged.iter().any(|c| c.entries.contains_key("key2")));
838 }
839
840 #[test]
841 fn test_merge_case6_is_host() {
842 let base = config_with_filters(
844 false,
845 None,
846 None,
847 None,
848 None,
849 None,
850 vec![make_entry_premerge(
851 "shared",
852 ServerConfigurationValue::String("base".into()),
853 )],
854 );
855
856 let specific = config_with_filters(
858 true,
859 None,
860 None,
861 None,
862 None,
863 None,
864 vec![make_entry_premerge(
865 "shared",
866 ServerConfigurationValue::String("specific".into()),
867 )],
868 );
869
870 let merged = premerge_configuration(vec![base, specific]);
871 assert_eq!(merged.len(), 2);
872
873 let entries = &merged[1].entries["shared"].inner;
874 assert_eq!(entries.len(), 1);
875 assert_eq!(entries[0].values[0].as_str(), Some("specific"));
876 }
877
878 #[test]
879 fn test_merge_case5_port() {
880 let base = config_with_filters(
882 true,
883 None,
884 None,
885 None,
886 None,
887 None,
888 vec![make_entry_premerge(
889 "shared",
890 ServerConfigurationValue::String("base".into()),
891 )],
892 );
893
894 let specific = config_with_filters(
896 true,
897 None,
898 None,
899 Some(80),
900 None,
901 None,
902 vec![make_entry_premerge(
903 "shared",
904 ServerConfigurationValue::String("specific".into()),
905 )],
906 );
907
908 let merged = premerge_configuration(vec![base, specific]);
909 assert_eq!(merged.len(), 2);
910
911 let entries = &merged[1].entries["shared"].inner;
912 assert_eq!(entries.len(), 1);
913 assert_eq!(entries[0].values[0].as_str(), Some("specific"));
914 }
915
916 #[test]
917 fn test_merge_case1_error_handler() {
918 let base = config_with_filters(
919 true,
920 Some("host"),
921 Some(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))),
922 Some(3000),
923 Some("/api"),
924 None,
925 vec![make_entry_premerge(
926 "eh",
927 ServerConfigurationValue::String("base".into()),
928 )],
929 );
930
931 let specific = config_with_filters(
932 true,
933 Some("host"),
934 Some(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4))),
935 Some(3000),
936 Some("/api"),
937 Some(ErrorHandlerStatus::Any),
938 vec![make_entry_premerge(
939 "eh",
940 ServerConfigurationValue::String("specific".into()),
941 )],
942 );
943
944 let merged = premerge_configuration(vec![base, specific]);
945 assert_eq!(merged.len(), 2);
946
947 let entries = &merged[1].entries["eh"].inner;
948 assert_eq!(entries.len(), 1);
949 assert_eq!(entries[0].values[0].as_str(), Some("specific"));
950 }
951
952 #[test]
953 fn test_merge_preserves_specificity_order() {
954 let configs = vec![
955 config_with_filters(
956 true,
957 None,
958 None,
959 None,
960 None,
961 None,
962 vec![make_entry_premerge("a", ServerConfigurationValue::String("v1".into()))],
963 ),
964 config_with_filters(
965 true,
966 None,
967 None,
968 Some(80),
969 None,
970 None,
971 vec![make_entry_premerge("a", ServerConfigurationValue::String("v2".into()))],
972 ),
973 config_with_filters(
974 true,
975 Some("host"),
976 None,
977 Some(80),
978 None,
979 None,
980 vec![make_entry_premerge("a", ServerConfigurationValue::String("v3".into()))],
981 ),
982 ];
983
984 let merged = premerge_configuration(configs);
985 assert_eq!(merged.len(), 3);
986
987 let entries = &merged[2].entries["a"].inner;
988 assert_eq!(entries.len(), 1);
989 assert_eq!(entries[0].values[0].as_str(), Some("v3"));
990 }
991}