ferron/util/
error_config.rs

1use 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())); // still an array or ignored
222    assert_eq!(result["message"].as_str().unwrap(), "Not Found");
223  }
224}