From 96f66e6e6d47fd5073184ef936e8e1c1394a8fd2 Mon Sep 17 00:00:00 2001 From: Andrew Langmeier Date: Sat, 10 Feb 2024 00:47:25 -0500 Subject: [PATCH 1/4] Add a simple merge filter --- src/builtins/filters/object.rs | 51 ++++++++++++++++++++++++++++++++-- src/tera.rs | 1 + 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/builtins/filters/object.rs b/src/builtins/filters/object.rs index f9981450..7252bf0c 100644 --- a/src/builtins/filters/object.rs +++ b/src/builtins/filters/object.rs @@ -1,7 +1,8 @@ -/// Filters operating on numbers +/// Filters operating on objects use std::collections::HashMap; use serde_json::value::Value; +use serde_json::value::to_value; use crate::errors::{Error, Result}; @@ -10,7 +11,7 @@ pub fn get(value: &Value, args: &HashMap) -> Result { let default = args.get("default"); let key = match args.get("key") { Some(val) => try_get_value!("get", "key", String, val), - None => return Err(Error::msg("The `get` filter has to have an `key` argument")), + None => return Err(Error::msg("The `get` filter has to have a `key` argument")), }; match value.as_object() { @@ -29,10 +30,31 @@ pub fn get(value: &Value, args: &HashMap) -> Result { } } +/// Merge two objects, the second object is indicated by the `other` argument. +/// The second object's values will overwrite the first's in the event of a key conflict. +pub fn merge(value: &Value, args: &HashMap) -> Result { + let left = match value.as_object() { + Some(val) => val, + None => return Err(Error::msg("Filter `merge` was used on a value that isn't an object")), + }; + match args.get("other") { + Some(val) => match val.as_object() { + Some(right) => { + let mut result = left.clone(); + result.extend(right.clone()); + // We've already confirmed both sides were HashMaps, the result is a HashMap - + // - so unwrap + Ok(to_value(result).unwrap()) + }, + None => Err(Error::msg("The `other` argument for the `get` filter must be an object")) + }, + None => Err(Error::msg("The `merge` filter has to have an `other` argument")), + } +} + #[cfg(test)] mod tests { use super::*; - use serde_json::value::to_value; use std::collections::HashMap; #[test] @@ -87,4 +109,27 @@ mod tests { assert!(result.is_ok()); assert_eq!(result.unwrap(), to_value("default").unwrap()); } + + #[test] + fn test_merge_filter() { + let mut obj_1 = HashMap::new(); + obj_1.insert("1".to_string(), "first".to_string()); + obj_1.insert("2".to_string(), "second".to_string()); + + let mut obj_2 = HashMap::new(); + obj_2.insert("2".to_string(), "SECOND".to_string()); + obj_2.insert("3".to_string(), "third".to_string()); + + let mut args = HashMap::new(); + args.insert("other".to_string(), to_value(obj_2).unwrap()); + + let result = merge(&to_value(&obj_1).unwrap(), &args); + assert!(result.is_ok()); + + let mut expected = HashMap::new(); + expected.insert("1".to_string(), "first".to_string()); + expected.insert("2".to_string(), "SECOND".to_string()); + expected.insert("3".to_string(), "third".to_string()); + assert_eq!(result.unwrap(), to_value(expected).unwrap()); + } } diff --git a/src/tera.rs b/src/tera.rs index 0c694f5c..33ac97b6 100644 --- a/src/tera.rs +++ b/src/tera.rs @@ -726,6 +726,7 @@ impl Tera { self.register_filter("as_str", common::as_str); self.register_filter("get", object::get); + self.register_filter("merge", object::merge); } fn register_tera_testers(&mut self) { From 4ebdac29d01e608878fd5f1a3cafa7dbba433e5e Mon Sep 17 00:00:00 2001 From: Andrew Langmeier Date: Sat, 10 Feb 2024 01:02:41 -0500 Subject: [PATCH 2/4] other -> with --- src/builtins/filters/object.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/builtins/filters/object.rs b/src/builtins/filters/object.rs index 7252bf0c..84d4ad56 100644 --- a/src/builtins/filters/object.rs +++ b/src/builtins/filters/object.rs @@ -30,14 +30,14 @@ pub fn get(value: &Value, args: &HashMap) -> Result { } } -/// Merge two objects, the second object is indicated by the `other` argument. +/// Merge two objects, the second object is indicated by the `with` argument. /// The second object's values will overwrite the first's in the event of a key conflict. pub fn merge(value: &Value, args: &HashMap) -> Result { let left = match value.as_object() { Some(val) => val, None => return Err(Error::msg("Filter `merge` was used on a value that isn't an object")), }; - match args.get("other") { + match args.get("with") { Some(val) => match val.as_object() { Some(right) => { let mut result = left.clone(); @@ -46,9 +46,9 @@ pub fn merge(value: &Value, args: &HashMap) -> Result { // - so unwrap Ok(to_value(result).unwrap()) }, - None => Err(Error::msg("The `other` argument for the `get` filter must be an object")) + None => Err(Error::msg("The `with` argument for the `get` filter must be an object")) }, - None => Err(Error::msg("The `merge` filter has to have an `other` argument")), + None => Err(Error::msg("The `merge` filter has to have an `with` argument")), } } @@ -121,7 +121,7 @@ mod tests { obj_2.insert("3".to_string(), "third".to_string()); let mut args = HashMap::new(); - args.insert("other".to_string(), to_value(obj_2).unwrap()); + args.insert("with".to_string(), to_value(obj_2).unwrap()); let result = merge(&to_value(&obj_1).unwrap(), &args); assert!(result.is_ok()); From fddf4c455cd57485020e2c5757e17d8388426c7c Mon Sep 17 00:00:00 2001 From: Andrew Langmeier Date: Sat, 10 Feb 2024 01:02:56 -0500 Subject: [PATCH 3/4] cargo fmt --- src/builtins/filters/object.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/builtins/filters/object.rs b/src/builtins/filters/object.rs index 84d4ad56..5a6d0506 100644 --- a/src/builtins/filters/object.rs +++ b/src/builtins/filters/object.rs @@ -1,8 +1,8 @@ /// Filters operating on objects use std::collections::HashMap; -use serde_json::value::Value; use serde_json::value::to_value; +use serde_json::value::Value; use crate::errors::{Error, Result}; @@ -45,10 +45,10 @@ pub fn merge(value: &Value, args: &HashMap) -> Result { // We've already confirmed both sides were HashMaps, the result is a HashMap - // - so unwrap Ok(to_value(result).unwrap()) - }, - None => Err(Error::msg("The `with` argument for the `get` filter must be an object")) + } + None => Err(Error::msg("The `with` argument for the `get` filter must be an object")), }, - None => Err(Error::msg("The `merge` filter has to have an `with` argument")), + None => Err(Error::msg("The `merge` filter has to have an `with` argument")), } } From b929f343277f720ab53fff01db39e4410d4a3a27 Mon Sep 17 00:00:00 2001 From: Andrew Langmeier Date: Sat, 10 Feb 2024 01:08:27 -0500 Subject: [PATCH 4/4] Reduce nesting and fix typo --- src/builtins/filters/object.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/builtins/filters/object.rs b/src/builtins/filters/object.rs index 5a6d0506..26677bd2 100644 --- a/src/builtins/filters/object.rs +++ b/src/builtins/filters/object.rs @@ -37,18 +37,19 @@ pub fn merge(value: &Value, args: &HashMap) -> Result { Some(val) => val, None => return Err(Error::msg("Filter `merge` was used on a value that isn't an object")), }; - match args.get("with") { - Some(val) => match val.as_object() { - Some(right) => { - let mut result = left.clone(); - result.extend(right.clone()); - // We've already confirmed both sides were HashMaps, the result is a HashMap - - // - so unwrap - Ok(to_value(result).unwrap()) - } - None => Err(Error::msg("The `with` argument for the `get` filter must be an object")), - }, - None => Err(Error::msg("The `merge` filter has to have an `with` argument")), + let with = match args.get("with") { + Some(val) => val, + None => return Err(Error::msg("The `merge` filter has to have a `with` argument")), + }; + match with.as_object() { + Some(right) => { + let mut result = left.clone(); + result.extend(right.clone()); + // We've already confirmed both sides were HashMaps, the result is a HashMap - + // - so unwrap + Ok(to_value(result).unwrap()) + } + None => Err(Error::msg("The `with` argument for the `get` filter must be an object")), } }