1 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4 // option. This file may not be copied, modified, or distributed
5 // except according to those terms.
6
7 use crates::thiserror::Error;
8 use crates::yaml_rust::yaml::{Array, Hash};
9 use crates::yaml_rust::Yaml;
10
11 /// Errors which may occur when performing the YAML merge key process.
12 ///
13 /// This enum is `non_exhaustive`, but cannot be marked as such until it is stable. In the
14 /// meantime, there is a hidden variant.
15 #[derive(Debug, Error)]
16 // TODO: #[non_exhaustive]
17 pub enum MergeKeyError {
18 /// A non-hash value was given as a value to merge into a hash.
19 ///
20 /// This happens with a document such as:
21 ///
22 /// ```yaml
23 /// -
24 /// <<: 4
25 /// x: 1
26 /// ```
27 #[error("only mappings and arrays of mappings may be merged")]
28 InvalidMergeValue,
29 /// This is here to force `_` matching right now.
30 ///
31 /// **DO NOT USE**
32 #[doc(hidden)]
33 #[error("unreachable...")]
34 _NonExhaustive,
35 }
36
37 lazy_static! {
38 /// The name of the key to use for merge data.
39 static ref MERGE_KEY: Yaml = Yaml::String("<<".into());
40 }
41
42 /// Merge two hashes together.
merge_hashes(mut hash: Hash, rhs: Hash) -> Hash43 fn merge_hashes(mut hash: Hash, rhs: Hash) -> Hash {
44 rhs.into_iter().for_each(|(key, value)| {
45 hash.entry(key).or_insert(value);
46 });
47 hash
48 }
49
50 /// Merge values together.
merge_values(hash: Hash, value: Yaml) -> Result<Hash, MergeKeyError>51 fn merge_values(hash: Hash, value: Yaml) -> Result<Hash, MergeKeyError> {
52 let merge_values = match value {
53 Yaml::Array(arr) => {
54 let init: Result<Hash, _> = Ok(Hash::new());
55
56 arr.into_iter().fold(init, |res_hash, item| {
57 // Merge in the next item.
58 res_hash.and_then(move |res_hash| {
59 if let Yaml::Hash(next_hash) = item {
60 Ok(merge_hashes(res_hash, next_hash))
61 } else {
62 // Non-hash values at this level are not allowed.
63 Err(MergeKeyError::InvalidMergeValue)
64 }
65 })
66 })?
67 },
68 Yaml::Hash(merge_hash) => merge_hash,
69 _ => return Err(MergeKeyError::InvalidMergeValue),
70 };
71
72 Ok(merge_hashes(hash, merge_values))
73 }
74
75 /// Recurse into a hash and handle items with merge keys in them.
merge_hash(hash: Hash) -> Result<Yaml, MergeKeyError>76 fn merge_hash(hash: Hash) -> Result<Yaml, MergeKeyError> {
77 let mut hash = hash
78 .into_iter()
79 // First handle any merge keys in the key or value...
80 .map(|(key, value)| {
81 merge_keys(key).and_then(|key| merge_keys(value).map(|value| (key, value)))
82 })
83 .collect::<Result<Hash, _>>()?;
84
85 if let Some(merge_value) = hash.remove(&MERGE_KEY) {
86 merge_values(hash, merge_value).map(Yaml::Hash)
87 } else {
88 Ok(Yaml::Hash(hash))
89 }
90 }
91
92 /// Recurse into an array and handle items with merge keys in them.
merge_array(arr: Array) -> Result<Yaml, MergeKeyError>93 fn merge_array(arr: Array) -> Result<Yaml, MergeKeyError> {
94 arr.into_iter()
95 .map(merge_keys)
96 .collect::<Result<Array, _>>()
97 .map(Yaml::Array)
98 }
99
100 /// Handle merge keys in a YAML document.
merge_keys(doc: Yaml) -> Result<Yaml, MergeKeyError>101 pub fn merge_keys(doc: Yaml) -> Result<Yaml, MergeKeyError> {
102 match doc {
103 Yaml::Hash(hash) => merge_hash(hash),
104 Yaml::Array(arr) => merge_array(arr),
105 _ => Ok(doc),
106 }
107 }
108