fel4_config/
manifest.rs

1/// Related to the parsing and representation of the full fel4 manifest
2use multimap::MultiMap;
3use std::collections::{BTreeMap, HashMap, HashSet};
4use std::fs::File;
5use std::io::prelude::*;
6use std::path::Path;
7use toml;
8
9use super::ConfigError;
10use types::*;
11/// The full content of a fel4 manifest
12#[derive(Clone, Debug, PartialEq)]
13pub struct FullFel4Manifest {
14    pub artifact_path: String,
15    pub target_specs_path: String,
16    pub selected_target: SupportedTarget,
17    pub selected_platform: SupportedPlatform,
18    pub targets: HashMap<SupportedTarget, FullFel4Target>,
19}
20
21/// The full content of a target within a fel4 manifest
22#[derive(Clone, Debug, PartialEq)]
23pub struct FullFel4Target {
24    pub identity: SupportedTarget,
25    pub direct_properties: Vec<FlatTomlProperty>,
26    pub build_profile_properties: MultiMap<BuildProfile, FlatTomlProperty>,
27    pub platform_properties: MultiMap<SupportedPlatform, FlatTomlProperty>,
28}
29
30/// Retrieve the complete contents of the fel4 toml from a file
31pub fn get_full_manifest<P: AsRef<Path>>(path: P) -> Result<FullFel4Manifest, ConfigError> {
32    let mut manifest_file = File::open(&path).map_err(|_| ConfigError::FileReadFailure)?;
33    let mut toml_string = String::new();
34    let _size = manifest_file
35        .read_to_string(&mut toml_string)
36        .map_err(|_| ConfigError::FileReadFailure)?;
37    parse_full_manifest(toml_string)
38}
39
40/// Retrieve the complete contents of the fel4 toml from a string
41pub fn parse_full_manifest<S: AsRef<str>>(toml_string: S) -> Result<FullFel4Manifest, ConfigError> {
42    let manifest = toml_string
43        .as_ref()
44        .parse::<toml::Value>()
45        .map_err(|_| ConfigError::TomlParseFailure)?;
46    toml_to_full_manifest(&manifest)
47}
48
49#[derive(Clone, Debug, PartialEq)]
50struct Fel4Header {
51    pub artifact_path: String,
52    pub target_specs_path: String,
53    pub selected_target: SupportedTarget,
54    pub selected_platform: SupportedPlatform,
55}
56
57/// Internal convenience to break out the header table parsing
58fn parse_fel4_header(raw: &toml::Value) -> Result<Fel4Header, ConfigError> {
59    let fel4_table = raw
60        .get("fel4")
61        .and_then(toml::Value::as_table)
62        .ok_or_else(|| ConfigError::MissingTable("fel4".into()))?;
63
64    has_only_approved_substructures(fel4_table, None)
65        .map_err(|name| ConfigError::UnexpectedStructure(format!("fel4.{}", name)))?;
66
67    let selected_target: SupportedTarget = fel4_table
68        .get("target")
69        .and_then(toml::Value::as_str)
70        .and_then(|s| if s.is_empty() { None } else { Some(s) })
71        .ok_or_else(|| ConfigError::MissingRequiredProperty("fel4".into(), "target".into()))?
72        .parse()
73        .map_err(|e| {
74            ConfigError::InvalidValueOption("target", SupportedTarget::target_names(), e)
75        })?;
76    let selected_platform: SupportedPlatform = fel4_table
77        .get("platform")
78        .and_then(toml::Value::as_str)
79        .and_then(|s| if s.is_empty() { None } else { Some(s) })
80        .ok_or_else(|| ConfigError::MissingRequiredProperty("fel4".into(), "platform".into()))?
81        .parse()
82        .map_err(|e| {
83            ConfigError::InvalidValueOption("platform", SupportedPlatform::platform_names(), e)
84        })?;
85
86    let artifact_path = fel4_table
87        .get("artifact-path")
88        .ok_or_else(|| ConfigError::MissingRequiredProperty("fel4".into(), "artifact-path".into()))
89        .and_then(|o| {
90            o.as_str()
91                .ok_or_else(|| ConfigError::NonStringProperty("artifact-path"))
92        })
93        .and_then(|s| {
94            if s.is_empty() {
95                Err(ConfigError::MissingRequiredProperty(
96                    "fel4".into(),
97                    "artifact-path".into(),
98                ))
99            } else {
100                Ok(s)
101            }
102        })?
103        .to_string();
104    let target_specs_path = fel4_table
105        .get("target-specs-path")
106        .ok_or_else(|| {
107            ConfigError::MissingRequiredProperty("fel4".into(), "target-specs-path".into())
108        })
109        .and_then(|o| {
110            o.as_str()
111                .ok_or_else(|| ConfigError::NonStringProperty("target-specs-path"))
112        })
113        .and_then(|s| {
114            if s.is_empty() {
115                Err(ConfigError::MissingRequiredProperty(
116                    "fel4".into(),
117                    "target-specs-path".into(),
118                ))
119            } else {
120                Ok(s)
121            }
122        })?
123        .to_string();
124    Ok(Fel4Header {
125        artifact_path,
126        target_specs_path,
127        selected_target,
128        selected_platform,
129    })
130}
131
132/// Parse the complete contents of the fel4 toml
133pub fn toml_to_full_manifest(raw: &toml::Value) -> Result<FullFel4Manifest, ConfigError> {
134    let Fel4Header {
135        artifact_path,
136        target_specs_path,
137        selected_target,
138        selected_platform,
139    } = parse_fel4_header(&raw)?;
140
141    // Parse the target subtables
142    let allowed_target_subtable_names: HashSet<String> = SupportedPlatform::platform_names()
143        .into_iter()
144        .chain(BuildProfile::build_profile_names().into_iter())
145        .collect();
146    let mut targets: HashMap<SupportedTarget, FullFel4Target> = HashMap::new();
147    for curr_target in SupportedTarget::targets() {
148        let curr_target_name = curr_target.full_name();
149        let curr_target_table = match raw.get(curr_target_name).and_then(toml::Value::as_table) {
150            None => continue,
151            Some(t) => t,
152        };
153        has_only_approved_substructures(curr_target_table, Some(&allowed_target_subtable_names))
154            .map_err(|prop_name| {
155                ConfigError::UnexpectedStructure(format!("{}.{}", curr_target_name, prop_name))
156            })?;
157
158        let mut build_profile_properties: MultiMap<BuildProfile, FlatTomlProperty> =
159            MultiMap::new();
160        for profile in BuildProfile::build_profiles() {
161            let profile_name = profile.full_name();
162            let properties = match curr_target_table
163                .get(profile_name)
164                .and_then(toml::Value::as_table)
165            {
166                None => continue,
167                Some(t) => extract_flat_properties(t).map_err(|prop_name| {
168                    ConfigError::UnexpectedStructure(format!(
169                        "{}.{}.{}",
170                        curr_target_name, profile_name, prop_name
171                    ))
172                })?,
173            };
174            build_profile_properties
175                .entry(profile)
176                .or_insert_vec(properties);
177        }
178        let mut platform_properties: MultiMap<SupportedPlatform, FlatTomlProperty> =
179            MultiMap::new();
180        for platform in SupportedPlatform::platforms() {
181            let platform_name = platform.full_name();
182            let properties = match curr_target_table
183                .get(platform_name)
184                .and_then(toml::Value::as_table)
185            {
186                None => continue,
187                Some(t) => extract_flat_properties(t).map_err(|prop_name| {
188                    ConfigError::UnexpectedStructure(format!(
189                        "{}.{}.{}",
190                        curr_target_name, platform_name, prop_name
191                    ))
192                })?,
193            };
194            platform_properties
195                .entry(platform)
196                .or_insert_vec(properties);
197        }
198
199        let table_minus_approved_subtables = curr_target_table
200            .iter()
201            .filter(|&(k, _)| !allowed_target_subtable_names.contains(&(*k).clone()))
202            .map(|(k, v)| (k.clone(), v.clone()))
203            .collect();
204        let direct_properties =
205            extract_flat_properties(&table_minus_approved_subtables).map_err(|prop_name| {
206                ConfigError::UnexpectedStructure(format!("{}.{}", curr_target_name, prop_name))
207            })?;
208
209        targets.insert(
210            curr_target,
211            FullFel4Target {
212                identity: curr_target,
213                direct_properties,
214                build_profile_properties,
215                platform_properties,
216            },
217        );
218    }
219
220    Ok(FullFel4Manifest {
221        artifact_path,
222        target_specs_path,
223        selected_target,
224        selected_platform,
225        targets,
226    })
227}
228
229fn has_only_approved_substructures(
230    map: &BTreeMap<String, toml::Value>,
231    approved_substructures: Option<&HashSet<String>>,
232) -> Result<(), String> {
233    for (k, v) in map {
234        match v {
235            &toml::Value::Array(_) | toml::Value::Table(_) => {
236                if let Some(substructure_whitelist) = approved_substructures {
237                    if substructure_whitelist.contains(k) {
238                        continue;
239                    } else {
240                        return Err(k.to_string());
241                    }
242                } else {
243                    return Err(k.to_string());
244                }
245            }
246            _ => continue,
247        }
248    }
249    Ok(())
250}
251
252fn extract_flat_properties(
253    table: &BTreeMap<String, toml::Value>,
254) -> Result<Vec<FlatTomlProperty>, String> {
255    let mut v = Vec::new();
256    for (prop_name, value) in table {
257        let flat_value = match value {
258            &toml::Value::String(ref v) => FlatTomlValue::String(v.to_string()),
259            &toml::Value::Integer(v) => FlatTomlValue::Integer(v),
260            &toml::Value::Float(v) => FlatTomlValue::Float(v),
261            &toml::Value::Boolean(v) => FlatTomlValue::Boolean(v),
262            &toml::Value::Datetime(ref v) => FlatTomlValue::Datetime(v.clone()),
263            &toml::Value::Array(_) | toml::Value::Table(_) => {
264                return Err(prop_name.to_string());
265            }
266        };
267        v.push(FlatTomlProperty::new(prop_name.to_string(), flat_value));
268    }
269    Ok(v)
270}
271#[cfg(test)]
272mod tests {
273    use super::*;
274    use std::path::PathBuf;
275
276    #[test]
277    fn bogus_file_unreadable() {
278        assert_eq!(
279            Err(ConfigError::FileReadFailure),
280            get_full_manifest(PathBuf::from("path/to/nowhere"))
281        );
282    }
283
284    #[test]
285    fn non_toml_file_unparseable() {
286        assert_eq!(
287            Err(ConfigError::TomlParseFailure),
288            parse_full_manifest("<hey>not toml</hey>")
289        );
290    }
291
292    #[test]
293    fn toml_file_without_fel4_table() {
294        assert_eq!(
295            Err(ConfigError::MissingTable("fel4".into())),
296            parse_full_manifest(
297                "just = true
298            some = \"unrelated property\""
299            )
300        );
301    }
302
303    #[test]
304    fn fel4_table_missing_target() {
305        assert_eq!(
306            Err(ConfigError::MissingRequiredProperty(
307                "fel4".into(),
308                "target".into()
309            )),
310            parse_full_manifest(
311                r#"[fel4]
312            wrong_properties = true"#
313            )
314        );
315    }
316
317    #[test]
318    fn fel4_table_invalid_target() {
319        assert_eq!(
320            Err(ConfigError::InvalidValueOption(
321                "target",
322                SupportedTarget::target_names(),
323                "wrong".into()
324            )),
325            parse_full_manifest(
326                r#"[fel4]
327            target = "wrong""#
328            )
329        );
330    }
331
332    #[test]
333    fn fel4_table_missing_platform() {
334        assert_eq!(
335            Err(ConfigError::MissingRequiredProperty(
336                "fel4".into(),
337                "platform".into()
338            )),
339            parse_full_manifest(
340                r#"[fel4]
341            target = "x86_64-sel4-fel4""#
342            )
343        );
344    }
345
346    #[test]
347    fn fel4_table_invalid_platform() {
348        assert_eq!(
349            Err(ConfigError::InvalidValueOption(
350                "platform",
351                SupportedPlatform::platform_names(),
352                "wrong".into()
353            )),
354            parse_full_manifest(
355                r#"[fel4]
356            target = "x86_64-sel4-fel4"
357            platform = "wrong""#
358            )
359        );
360    }
361
362    #[test]
363    fn fel4_table_missing_artifact_path() {
364        assert_eq!(
365            Err(ConfigError::MissingRequiredProperty(
366                "fel4".into(),
367                "artifact-path".into()
368            )),
369            parse_full_manifest(
370                r#"[fel4]
371            target = "x86_64-sel4-fel4"
372            platform = "pc99""#
373            )
374        );
375    }
376
377    #[test]
378    fn fel4_table_missing_target_specs_path() {
379        assert_eq!(
380            Err(ConfigError::MissingRequiredProperty(
381                "fel4".into(),
382                "target-specs-path".into()
383            )),
384            parse_full_manifest(
385                r#"[fel4]
386            target = "x86_64-sel4-fel4"
387            platform = "pc99"
388            artifact-path = "artifacts/path/nested""#
389            )
390        );
391    }
392
393    #[test]
394    fn wrong_type_target_specs_path_in_fel4() {
395        assert_eq!(
396            Err(ConfigError::NonStringProperty("target-specs-path")),
397            parse_full_manifest(
398                r#"[fel4]
399            target = "x86_64-sel4-fel4"
400            platform = "pc99"
401            artifact-path = "somewhere"
402            target-specs-path = true
403            "#
404            )
405        );
406    }
407
408    #[test]
409    fn wrong_type_artifact_path_in_fel4() {
410        assert_eq!(
411            Err(ConfigError::NonStringProperty("artifact-path")),
412            parse_full_manifest(
413                r#"[fel4]
414            target = "x86_64-sel4-fel4"
415            platform = "pc99"
416            artifact-path = true
417            target-specs-path = "where/are/rust/targets"
418            "#
419            )
420        );
421    }
422
423    #[test]
424    fn unexpected_structure_in_fel4() {
425        assert_eq!(
426            Err(ConfigError::UnexpectedStructure("fel4.custom".into())),
427            parse_full_manifest(
428                r#"[fel4]
429            target = "x86_64-sel4-fel4"
430            platform = "pc99"
431            artifact-path = "artifacts/path/nested"
432            target-specs-path = "where/are/rust/targets"
433            [fel4.custom]
434            SomeProp = "hello"
435            "#
436            )
437        );
438    }
439
440    #[test]
441    fn unexpected_structure_in_target() {
442        assert_eq!(
443            Err(ConfigError::UnexpectedStructure(
444                "x86_64-sel4-fel4.custom".into()
445            )),
446            parse_full_manifest(
447                r#"[fel4]
448            target = "x86_64-sel4-fel4"
449            platform = "pc99"
450            artifact-path = "artifacts/path/nested"
451            target-specs-path = "where/are/rust/targets"
452            [x86_64-sel4-fel4]
453            SomeProp = "hello"
454            [x86_64-sel4-fel4.custom]
455            NestedProp = true
456            "#
457            )
458        );
459    }
460
461    #[test]
462    fn unexpected_structure_in_target_platform() {
463        assert_eq!(
464            Err(ConfigError::UnexpectedStructure(
465                "x86_64-sel4-fel4.pc99.custom".into()
466            )),
467            parse_full_manifest(
468                r#"[fel4]
469            target = "x86_64-sel4-fel4"
470            platform = "pc99"
471            artifact-path = "artifacts/path/nested"
472            target-specs-path = "where/are/rust/targets"
473            [x86_64-sel4-fel4]
474            SomeProp = "hello"
475            [x86_64-sel4-fel4.pc99]
476            SomethingPlatformy = true
477            [x86_64-sel4-fel4.pc99.custom]
478            DeepNesting = true
479            "#
480            )
481        );
482    }
483
484    #[test]
485    fn unexpected_structure_in_target_build_profile() {
486        assert_eq!(
487            Err(ConfigError::UnexpectedStructure(
488                "x86_64-sel4-fel4.debug.custom".into()
489            )),
490            parse_full_manifest(
491                r#"[fel4]
492            target = "x86_64-sel4-fel4"
493            platform = "pc99"
494            artifact-path = "artifacts/path/nested"
495            target-specs-path = "where/are/rust/targets"
496            [x86_64-sel4-fel4]
497            SomeProp = "hello"
498            [x86_64-sel4-fel4.debug]
499            KernelPrinting = true
500            [x86_64-sel4-fel4.debug.custom]
501            DeepNesting = true
502            "#
503            )
504        );
505    }
506}