ovlach_pdf/src/tools/tera.rs
2023-12-03 21:43:12 +01:00

159 lines
5.8 KiB
Rust

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<Self, ()> {
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<String, Value>
) -> Result<Value, Error> {
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<String, Value>) -> Result<tera::Value, tera::Error> {
let mut arr = try_get_value!("filter", "value", Vec<Value>, 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::<Vec<_>>();
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<TestClass>, include_null: &str, value: Option<&str>) -> Result<Value, Error> {
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<String>,
}
impl TestClass {
pub fn new(against_value: Option<String>) -> Self {
TestClass { against_value }
}
}
fn generate_test_class(first_data: Option<String>, second_data: Option<String>) -> Vec<TestClass> {
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<TestClass> = vec![];
assert_eq!(result, json!(vec));
let result = call_advanced_filter(&data, "only", Some("zz")).is_err();
assert!(result);
}
}