159 lines
5.8 KiB
Rust
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);
|
|
}
|
|
}
|