| // Copyright 2024 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use anyhow::Result; |
| use serde::de::DeserializeOwned; |
| use serde_json::Value; |
| |
| pub fn try_merge_into<T: DeserializeOwned>(base: Value, overrides: Value) -> Result<T> { |
| let merged = merge(base, overrides); |
| Ok(serde_json::from_value(merged)?) |
| } |
| |
| fn merge(base: Value, value: Value) -> Value { |
| match (base, value) { |
| (base @ _, Value::Null) => { |
| // Override value is nothing, so nothing to do. |
| base |
| } |
| (Value::Object(mut merged), Value::Object(overrides)) => { |
| // Both are maps/dicts, so do a key-wise merging. |
| for (key, value) in overrides { |
| // Remove the existing value, so that it can be merged before re-inserting. |
| let existing = merged.remove(&key); |
| match existing { |
| Some(existing) => { |
| // If there's already a value, recursively merge the override value. |
| merged.insert(key, merge(existing, value)); |
| } |
| None => { |
| // If there's not an existing value, just use the override value. |
| merged.insert(key, value); |
| } |
| } |
| } |
| Value::Object(merged) |
| } |
| (_, value) => { |
| // Either the override or the base value is a non-mergeable type, |
| // so just return the override value. |
| value |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use serde_json::json; |
| |
| #[test] |
| fn test_both_null() { |
| let base = Value::Null; |
| let value = Value::Null; |
| |
| assert_eq!(Value::Null, merge(base, value)) |
| } |
| |
| #[test] |
| fn test_null_override_is_no_op() { |
| let base = json!("a string"); |
| assert_eq!("a string", merge(base, Value::Null)); |
| |
| let base = json!(1); |
| assert_eq!(1, merge(base, Value::Null)); |
| |
| let base = json!(true); |
| assert_eq!(true, merge(base, Value::Null)); |
| |
| let base = json!(["item1", "item2"]); |
| assert_eq!(json!(["item1", "item2"]), merge(base, Value::Null)); |
| } |
| |
| #[test] |
| fn test_null_base_is_overridden() { |
| let value = json!("a string"); |
| assert_eq!("a string", merge(Value::Null, value)); |
| } |
| |
| #[test] |
| fn test_scalar_value_is_overriden_by_scalar() { |
| assert_eq!(json!("override"), merge(json!(42), json!("override"))); |
| assert_eq!(json!(36), merge(json!(4), json!(36))); |
| assert_eq!(json!(42), merge(json!(false), json!(42))); |
| assert_eq!(json!(false), merge(json!(3), json!(false))); |
| assert_eq!(json!(["item1", "item2"]), merge(json!(39), json!(["item1", "item2"]))); |
| } |
| |
| #[test] |
| fn test_object_overrides_scalar_or_list() { |
| let value = json!({ |
| "key": "value" |
| }); |
| |
| assert_eq!(value, merge(Value::Null, value.clone())); |
| assert_eq!(value, merge(json!(true), value.clone())); |
| assert_eq!(value, merge(json!(42), value.clone())); |
| assert_eq!(value, merge(json!("a string"), value.clone())); |
| assert_eq!(value, merge(json!(["item1", "item2"]), value.clone())); |
| } |
| |
| #[test] |
| fn test_merged_keys() { |
| assert_eq!( |
| json!({ |
| "base_key": "a string", |
| "added_key": "some value" |
| }), |
| merge( |
| json!({ |
| "base_key": "a string" |
| }), |
| json!({ |
| "added_key": "some value" |
| }) |
| ) |
| ); |
| } |
| |
| #[test] |
| fn test_merged_keys_adds_object() { |
| assert_eq!( |
| json!({ |
| "base_key": "a string", |
| "added_key": { |
| "inner_key": "some value" |
| } |
| }), |
| merge( |
| json!({ |
| "base_key": "a string" |
| }), |
| json!({ |
| "added_key": { |
| "inner_key": "some value" |
| } |
| }) |
| ) |
| ); |
| } |
| |
| #[test] |
| fn test_merge_overrides_values() { |
| assert_eq!( |
| json!({ |
| "key1": "key1 value", |
| "key2": "key2 new value" |
| }), |
| merge( |
| json!({ |
| "key1": "key1 value", |
| "key2": "key2 value" |
| }), |
| json!({ |
| "key2": "key2 new value" |
| }) |
| ) |
| ); |
| } |
| |
| #[test] |
| fn test_merge_overrides_nested_value() { |
| assert_eq!( |
| json!({ |
| "key1": "key1 value", |
| "key2": { |
| "inner1": "inner 1 value", |
| "inner2": "inner 2 new value" |
| } |
| }), |
| merge( |
| json!({ |
| "key1": "key1 value", |
| "key2": { |
| "inner1": "inner 1 value", |
| "inner2": "inner 2 value" |
| } |
| }), |
| json!({ |
| "key2": { "inner2": "inner 2 new value" } |
| }) |
| ) |
| ); |
| } |
| |
| #[test] |
| fn test_merge_adds_empty_objects() { |
| assert_eq!( |
| json!({ |
| "key1": "key1 value", |
| "key2": {} |
| }), |
| merge( |
| json!({ |
| "key1": "key1 value" |
| }), |
| json!({ |
| "key2": {} |
| }) |
| ) |
| ); |
| } |
| } |