ferron/util/
error_config.rs1use yaml_rust2::Yaml;
2
3fn get_error_config(config: &Yaml, status_code: u16) -> Option<Yaml> {
4 if let Some(error_configs) = config["errorConfig"].as_vec() {
5 for error_config in error_configs {
6 let configured_status_code = error_config["scode"].as_i64();
7 if configured_status_code.is_none() || configured_status_code == Some(status_code as i64) {
8 return Some(error_config.to_owned());
9 }
10 }
11 }
12 None
13}
14
15pub fn combine_error_config(config: &Yaml, status_code: u16) -> Option<Yaml> {
16 if let Some(error_config) = get_error_config(config, status_code) {
17 if let Some(error_config_hash) = error_config.as_hash() {
18 if let Some(config_hash) = config.as_hash() {
19 let mut merged = config_hash.clone();
20 while merged.remove(&Yaml::from_str("errorConfig")).is_some() {}
21 for (key, value) in error_config_hash {
22 if let Some(key) = key.as_str() {
23 if key != "errorConfig" && key != "scode" {
24 match value {
25 Yaml::Array(host_array) => {
26 merged
27 .entry(Yaml::String(key.to_string()))
28 .and_modify(|global_val| {
29 if let Yaml::Array(global_array) = global_val {
30 global_array.extend(host_array.clone());
31 } else {
32 *global_val = Yaml::Array(host_array.clone());
33 }
34 })
35 .or_insert_with(|| Yaml::Array(host_array.clone()));
36 }
37 Yaml::Hash(host_hash) => {
38 merged
39 .entry(Yaml::String(key.to_string()))
40 .and_modify(|global_val| {
41 if let Yaml::Hash(global_hash) = global_val {
42 for (k, v) in host_hash {
43 global_hash.insert(k.clone(), v.clone());
44 }
45 } else {
46 *global_val = Yaml::Hash(host_hash.clone());
47 }
48 })
49 .or_insert_with(|| Yaml::Hash(host_hash.clone()));
50 }
51 _ => {
52 merged.insert(Yaml::String(key.to_string()), value.clone());
53 }
54 }
55 }
56 }
57 }
58 return Some(Yaml::Hash(merged));
59 }
60 }
61 }
62 None
63}
64
65#[cfg(test)]
66mod tests {
67 use yaml_rust2::{Yaml, YamlLoader};
68
69 use super::*;
70
71 fn load_yaml(input: &str) -> Yaml {
72 YamlLoader::load_from_str(input).unwrap()[0].clone()
73 }
74
75 #[test]
76 fn test_no_error_config() {
77 let yaml = load_yaml(
78 r#"
79 name: AppConfig
80 version: 1
81 "#,
82 );
83 assert_eq!(combine_error_config(&yaml, 404), None);
84 }
85
86 #[test]
87 fn test_no_matching_status_code() {
88 let yaml = load_yaml(
89 r#"
90 errorConfig:
91 - scode: 500
92 message: "Internal Server Error"
93 "#,
94 );
95 assert_eq!(combine_error_config(&yaml, 404), None);
96 }
97
98 #[test]
99 fn test_catch_all_error_config() {
100 let yaml = load_yaml(
101 r#"
102 errorConfig:
103 - message: "An error occurred"
104 "#,
105 );
106 let result = combine_error_config(&yaml, 500).unwrap();
107 assert_eq!(result["message"].as_str().unwrap(), "An error occurred");
108 }
109
110 #[test]
111 fn test_simple_merge() {
112 let yaml = load_yaml(
113 r#"
114 name: AppConfig
115 errorConfig:
116 - scode: 404
117 message: "Not Found"
118 "#,
119 );
120 let result = combine_error_config(&yaml, 404).unwrap();
121 assert_eq!(result["message"].as_str().unwrap(), "Not Found");
122 assert_eq!(result["name"].as_str().unwrap(), "AppConfig");
123 }
124
125 #[test]
126 fn test_merge_array() {
127 let yaml = load_yaml(
128 r#"
129 paths: ["/home"]
130 errorConfig:
131 - scode: 404
132 paths: ["/error"]
133 "#,
134 );
135 let result = combine_error_config(&yaml, 404).unwrap();
136 let paths = result["paths"].as_vec().unwrap();
137 let path_strs: Vec<_> = paths.iter().map(|p| p.as_str().unwrap()).collect();
138 assert_eq!(path_strs, vec!["/home", "/error"]);
139 }
140
141 #[test]
142 fn test_merge_hash() {
143 let yaml = load_yaml(
144 r#"
145 settings:
146 theme: "light"
147 errorConfig:
148 - scode: 404
149 settings:
150 debug: true
151 "#,
152 );
153 let result = combine_error_config(&yaml, 404).unwrap();
154 let settings = result["settings"].as_hash().unwrap();
155 assert_eq!(
156 settings
157 .get(&Yaml::String("theme".into()))
158 .unwrap()
159 .as_str()
160 .unwrap(),
161 "light"
162 );
163 assert!(settings
164 .get(&Yaml::String("debug".into()))
165 .unwrap()
166 .as_bool()
167 .unwrap());
168 }
169
170 #[test]
171 fn test_override_non_array_with_array() {
172 let yaml = load_yaml(
173 r#"
174 value: 42
175 errorConfig:
176 - scode: 404
177 value: [1, 2, 3]
178 "#,
179 );
180 let result = combine_error_config(&yaml, 404).unwrap();
181 let arr = result["value"].as_vec().unwrap();
182 let values: Vec<_> = arr.iter().map(|v| v.as_i64().unwrap()).collect();
183 assert_eq!(values, vec![1, 2, 3]);
184 }
185
186 #[test]
187 fn test_override_non_hash_with_hash() {
188 let yaml = load_yaml(
189 r#"
190 meta: "info"
191 errorConfig:
192 - scode: 404
193 meta:
194 tag: "404"
195 "#,
196 );
197 let result = combine_error_config(&yaml, 404).unwrap();
198 let meta = result["meta"].as_hash().unwrap();
199 assert_eq!(
200 meta
201 .get(&Yaml::String("tag".into()))
202 .unwrap()
203 .as_str()
204 .unwrap(),
205 "404"
206 );
207 }
208
209 #[test]
210 fn test_ignore_error_config_key_in_merge() {
211 let yaml = load_yaml(
212 r#"
213 name: App
214 errorConfig:
215 - scode: 404
216 errorConfig: "should be ignored"
217 message: "Not Found"
218 "#,
219 );
220 let result = combine_error_config(&yaml, 404).unwrap();
221 assert!(result["errorConfig"] != Yaml::String("should be ignored".to_string())); assert_eq!(result["message"].as_str().unwrap(), "Not Found");
223 }
224}