use std::{collections::HashMap}; use nanobyte_tera::{l18n::translate_filter, date::{calculate_age, get_year}, string::insert_space_every, gravatar::gravatar_link}; use ovlach_tera::entity::lang_entity; use rocket::{request::{FromRequest, Outcome}, Request}; use tera::{Tera, Value, Error}; #[derive(Debug)] pub struct NanoTera(pub Tera); // Allows a route to access the span #[rocket::async_trait] impl<'r> FromRequest<'r> for NanoTera { type Error = (); async fn from_request(request: &'r Request<'_>) -> Outcome { let mut tera = Tera::new("templates/*").unwrap(); tera.register_filter("translate", translate_filter); tera.register_filter("calculate_age", calculate_age); tera.register_filter("insert_space_every", insert_space_every); tera.register_filter("gravatar_link", gravatar_link); // filters specific to API tera.register_filter("lang_entity", lang_entity); tera.register_filter("format_date", get_year); // deprecated tera.register_filter("get_year", get_year); tera.register_filter("strip_proto", strip_proto); tera.register_filter("advanced_filter", advanced_filter); rocket::outcome::Outcome::Success(NanoTera(tera)) } } /// Strip protocol from URL (value) pub fn strip_proto( tera_value: &Value, _: &HashMap ) -> Result { let value = tera_value.as_str().unwrap(); if value.starts_with("http://") { Ok(Value::String(value.strip_prefix("http://").unwrap().to_string())) } else if value.starts_with("https://") { Ok(Value::String(value.strip_prefix("https://").unwrap().to_string())) } else { Ok(Value::String(value.to_string())) } } use tera::{try_get_value, to_value}; // TODO: move to other library /// If the `value` is not passed, optionally discard all elements where the attribute is null. (include_null -> all, none, only) pub fn advanced_filter(value: &Value, args: &HashMap) -> Result { let mut arr = try_get_value!("filter", "value", Vec, value); if arr.is_empty() { return Ok(arr.into()); } let key = match args.get("attribute") { Some(val) => try_get_value!("filter", "attribute", String, val), None => return Err(Error::msg("The `filter` filter has to have an `attribute` argument")), }; let null = match args.get("include_null") { Some(val) => try_get_value!("filter", "attribute", String, val), None => "none".to_string(), }; let against_value = match args.get("value") { Some(val) => Some(try_get_value!("filter", "value", String, val)), None => None }; if null == "only" && against_value.is_some() { return Err(Error::msg("The `filter` filter cannot have both `include_null=only` and `value`")) } arr = arr .into_iter() .filter(|v| { let tested_value = v.get(key.clone()).unwrap_or(&Value::Null); if tested_value.is_null() { return match null.as_str() { "all" => true, "none" => false, "only" => true, _ => false, } } else if null != "only" { let val = tested_value.as_str(); match val { Some(match_v) => { match against_value.clone() { Some(against_v) => match_v == against_v, None => true, } } None => true, } } else { false } }) .collect::>(); Ok(to_value(arr).unwrap()) } #[cfg(test)] mod tests { use super::*; use serde_json::json; use std::collections::HashMap; fn call_advanced_filter(data: &Vec, include_null: &str, value: Option<&str>) -> Result { let mut args = HashMap::new(); args.insert("attribute".to_string(), json!("against_value")); args.insert("include_null".to_string(), json!(include_null)); if let Some(val) = value { args.insert("value".to_string(), json!(val)); } advanced_filter(&json!(data), &args) } #[derive(serde::Serialize, serde::Deserialize, Debug)] struct TestClass { against_value: Option, } impl TestClass { pub fn new(against_value: Option) -> Self { TestClass { against_value } } } fn generate_test_class(first_data: Option, second_data: Option) -> Vec { vec![TestClass::new(first_data), TestClass::new(second_data)] } #[test] fn test_advanced_filter() { let data = vec![TestClass::new(Some("foo".to_string())), TestClass::new(None)]; let result = call_advanced_filter(&data, "all", None).unwrap(); assert_eq!(result, json!(generate_test_class(Some("foo".to_string()), None))); let result = call_advanced_filter(&data, "none", None).unwrap(); assert_eq!(result, json!(vec![TestClass::new(Some("foo".to_string()))])); let result = call_advanced_filter(&data, "only", None).unwrap(); assert_eq!(result, json!(vec![TestClass::new(None)])); // Test filtering strings let result = call_advanced_filter(&data, "all", Some("foo")).unwrap(); assert_eq!(result, json!(vec![TestClass::new(Some("foo".to_string())), TestClass::new(None)])); let result = call_advanced_filter(&data, "none", Some("bar")).unwrap(); let vec: Vec = vec![]; assert_eq!(result, json!(vec)); let result = call_advanced_filter(&data, "only", Some("zz")).is_err(); assert!(result); } }