// Heavily borrowed from Bottlerocket's merge-toml crate. // See https://github.com/bottlerocket-os/bottlerocket/blob/v1.11.1/sources/api/storewolf/merge-toml/src/lib.rs use base64::decode; use resource_agent::provider::{IntoProviderError, ProviderError, ProviderResult, Resources}; use std::str::from_utf8; use toml::{map::Entry, Value}; /// This modifies the first given toml Value by inserting any values from the second Value. /// /// This is done recursively. Any time a scalar or array is seen, the left side is set to match /// the right side. Any time a table is seen, we iterate through the keys of the tables; if the /// left side does not have the key from the right side, it's inserted, otherwise we recursively /// merge the values in each table for that key. /// /// If at any point in the recursion the data types of the two values does not match, we error. pub fn merge_values<'a>(merge_from: &'a Value, merge_into: &'a mut Value) -> ProviderResult<()> { // If the types of left and right don't match, we have inconsistent models, and shouldn't try // to merge them. if !merge_into.same_type(merge_from) { IntoProviderError::context( None, Resources::Clear, "Cannot merge mismatched data types in given TOML", )? } match merge_from { // If we see a scalar, we replace the left with the right. We treat arrays like scalars so // behavior is clear - no question about whether we're appending right onto left, etc. Value::String(_) | Value::Integer(_) | Value::Float(_) | Value::Boolean(_) | Value::Datetime(_) | Value::Array(_) => *merge_into = merge_from.clone(), // If we see a table, we recursively merge each key. Value::Table(from) => { // We know the other side is a table because of the `ensure` above. let to = merge_into.as_table_mut().ok_or_else(|| { ProviderError::new_with_context( Resources::Clear, "Cannot merge mismatched data types in given TOML", ) })?; for (k_from, v_from) in from.iter() { // Check if the left has the same key as the right. match to.entry(k_from) { // If not, we can just insert the value. Entry::Vacant(e) => { e.insert(v_from.clone()); } // If so, we need to recursively merge; we don't want to replace an entire // table, for example, because the left may have some distinct inner keys. Entry::Occupied(ref mut e) => { merge_values(v_from, e.get_mut())?; } } } } } Ok(()) } pub fn decode_to_string(encoded_userdata: &String) -> ProviderResult<String> { Ok(from_utf8( &decode(encoded_userdata).context(Resources::Clear, "Failed to decode base64 TOML")?, ) .context(Resources::Clear, "Failed to decode base64 TOML")? .to_string()) } #[cfg(test)] mod test { use super::merge_values; use toml::toml; #[test] fn merge_1() { let mut left = toml! { top1 = "left top1" top2 = "left top2" [settings.inner] inner_setting1 = "left inner_setting1" inner_setting2 = "left inner_setting2" }; let right = toml! { top1 = "right top1" [settings] setting = "right setting" [settings.inner] inner_setting1 = "right inner_setting1" inner_setting3 = "right inner_setting3" }; // Can't comment inside this toml, unfortunately. // "top1" is being overwritten from right. // "top2" is only in the left and remains. // "setting" is only in the right side. // "inner" tests that recursion works; inner_setting1 is replaced, 2 is untouched, and // 3 is new. let expected = toml! { top1 = "right top1" top2 = "left top2" [settings] setting = "right setting" [settings.inner] inner_setting1 = "right inner_setting1" inner_setting2 = "left inner_setting2" inner_setting3 = "right inner_setting3" }; merge_values(&right, &mut left).unwrap(); assert_eq!(left, expected); } #[test] fn merge_2() { let mut left = toml! { top1 = "left top1" top2 = "left top2" [settings] setting = "left setting" [settings.inner] inner_setting1 = "left inner_setting1" inner_setting2 = "left inner_setting2" }; let right = toml! { top1 = "right top1" [settings.inner] inner_setting1 = "right inner_setting1" inner_setting3 = "right inner_setting3" }; let expected = toml! { top1 = "right top1" top2 = "left top2" [settings] setting = "left setting" [settings.inner] inner_setting1 = "right inner_setting1" inner_setting2 = "left inner_setting2" inner_setting3 = "right inner_setting3" }; merge_values(&right, &mut left).unwrap(); assert_eq!(left, expected); } #[test] fn merge_3() { let mut left = toml! { top1 = "left top1" }; let right = toml! { top1 = 12 }; assert!(merge_values(&right, &mut left).is_err()); } }