use tera::{Filter, Kwargs, State, TeraResult, Value};
pub struct FormFilter;
impl Filter<&Value, TeraResult<Value>> for FormFilter {
fn is_safe(&self) -> bool {
true
}
fn call(&self, value: &Value, kwargs: Kwargs, _: &State) -> TeraResult<Value> {
let Some(field_name) = kwargs.get::<&str>("field")? else {
return render_form_html(value);
};
if field_name == "js" {
return Ok(Value::safe_string(
&render_scripts(value).unwrap_or_default(),
));
}
let mut output = render_field(value, field_name)?;
if is_first_field_by_index(value, field_name)
&& let Some(csrf) = render_csrf(value)
{
output = format!("{}\n{}", csrf, output);
}
if is_last_field_by_index(value, field_name)
&& let Some(hp) = get_honeypot_html(value)
{
output = format!("{}\n{}", output, hp);
}
Ok(Value::safe_string(&output))
}
}
fn lookup<'v>(value: &'v Value, key: &'v str) -> Option<&'v Value> {
value.get_from_path(key).or_else(|| {
value
.get_from_path("form")
.and_then(|f| f.get_from_path(key))
})
}
fn field_index(field: &Value) -> Option<u64> {
field.get_from_path("index").and_then(|idx| idx.as_u64())
}
fn is_first_field_by_index(value: &Value, field_name: &str) -> bool {
let Some(fields) = lookup(value, "fields") else {
return false;
};
let Some(fields_obj) = fields.as_map() else {
return false;
};
let min_index = fields_obj
.values()
.filter(|f| f.get_from_path("name").and_then(|n| n.as_str()) != Some("csrf_token"))
.filter_map(field_index)
.min();
let current_index = fields.get_from_path(field_name).and_then(field_index);
match (min_index, current_index) {
(Some(min), Some(current)) => min == current,
_ => false,
}
}
fn is_last_field_by_index(value: &Value, field_name: &str) -> bool {
let Some(fields) = lookup(value, "fields") else {
return false;
};
let Some(fields_obj) = fields.as_map() else {
return false;
};
let max_index = fields_obj.values().filter_map(field_index).max();
let current_index = fields.get_from_path(field_name).and_then(field_index);
match (max_index, current_index) {
(Some(max), Some(current)) => max == current,
_ => false,
}
}
fn render_csrf(value: &Value) -> Option<String> {
find_rendered_fields(value)?
.get_from_path("csrf_token")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
}
fn get_honeypot_html(value: &Value) -> Option<String> {
lookup(value, "honeypot_html")
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
}
fn render_scripts(value: &Value) -> Option<String> {
lookup(value, "rendered_js")
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
}
fn render_field(value: &Value, field_name: &str) -> TeraResult<String> {
if let Some(fields) = find_rendered_fields(value)
&& let Some(html) = fields.get_from_path(field_name).and_then(|v| v.as_str())
{
return Ok(html.to_string());
}
Err(tera::Error::message(format!(
"Field '{}' not found in form",
field_name
)))
}
fn find_rendered_fields(value: &Value) -> Option<&Value> {
lookup(value, "rendered_fields")
}
fn render_form_html(value: &Value) -> TeraResult<Value> {
let Some(html) = find_html(value) else {
return Err(tera::Error::message(
"Cannot render form: no 'html' field found",
));
};
match get_honeypot_html(value) {
Some(hp) => Ok(Value::safe_string(&format!("{}\n{}", html, hp))),
None => Ok(Value::safe_string(&html)),
}
}
fn find_html(value: &Value) -> Option<String> {
if let Some(html) = lookup(value, "html").and_then(|v| v.as_str()) {
return Some(html.to_string());
}
if let Some(fields) = find_rendered_fields(value)
&& let Some(obj) = fields.as_map()
{
let html: Vec<&str> = obj.values().filter_map(|v| v.as_str()).collect();
if !html.is_empty() {
return Some(html.join("\n"));
}
}
None
}