pub use crate::forms::generic::GenericField;
use crate::forms::{
base::{CommonFieldConfig, FieldConfig, FormField, TextConfig},
options::LengthConstraint,
};
use crate::utils::{
password::{PasswordConfig, PasswordService},
trad::{t, tf},
};
use serde::Serialize;
use std::sync::Arc;
use tera::{Context, Tera};
use validator::{ValidateEmail, ValidateUrl};
#[derive(Clone, Serialize, Debug)]
pub struct TextField {
pub base: FieldConfig,
pub config: TextConfig,
pub format: SpecialFormat,
pub hash_password: bool, }
#[derive(Clone, Debug, Serialize, PartialEq)]
pub enum SpecialFormat {
None,
Email,
Url,
Password,
RichText,
Csrf,
}
impl CommonFieldConfig for TextField {
fn get_field_config(&self) -> &FieldConfig {
&self.base
}
fn get_field_config_mut(&mut self) -> &mut FieldConfig {
&mut self.base
}
}
impl TextField {
fn create(name: &str, type_field: &str, format: SpecialFormat) -> Self {
Self {
base: FieldConfig::new(name, type_field, "base_string"),
config: TextConfig::default(),
format,
hash_password: true,
}
}
pub fn no_hash(mut self) -> Self {
self.hash_password = false;
self
}
pub fn min_length(mut self, min: u32, msg: &str) -> Self {
self.config.min_length = Some(LengthConstraint {
value: min,
message: (!msg.is_empty()).then(|| msg.to_string()),
});
self
}
pub fn max_length(mut self, max: u32, msg: &str) -> Self {
self.config.max_length = Some(LengthConstraint {
value: max,
message: (!msg.is_empty()).then(|| msg.to_string()),
});
self
}
pub fn text(name: &str) -> Self {
Self::create(name, "text", SpecialFormat::None)
}
pub fn textarea(name: &str) -> Self {
Self::create(name, "textarea", SpecialFormat::None)
}
pub fn richtext(name: &str) -> Self {
Self::create(name, "richtext", SpecialFormat::RichText)
}
pub fn password(name: &str) -> Self {
Self::create(name, "password", SpecialFormat::Password)
}
pub fn email(name: &str) -> Self {
let mut field = Self::create(name, "email", SpecialFormat::Email);
field.base.value = field.base.value.to_lowercase();
field
}
pub fn url(name: &str) -> Self {
Self::create(name, "url", SpecialFormat::Url)
}
pub fn required(mut self) -> Self {
self.set_required(true, None);
self
}
pub fn label(mut self, label: &str) -> Self {
self.base.label = label.to_string();
self
}
pub fn placeholder(mut self, p: &str) -> Self {
self.set_placeholder(p);
self
}
pub fn readonly(mut self, msg: &str) -> Self {
self.set_readonly(true, Some(msg));
self
}
pub fn disabled(mut self, msg: &str) -> Self {
self.set_disabled(true, Some(msg));
self
}
pub fn rows(mut self, rows: usize) -> Self {
self.base
.extra_context
.insert("rows".to_string(), serde_json::json!(rows));
self
}
}
impl FormField for TextField {
fn set_value(&mut self, value: &str) {
let cleaned = match self.format {
SpecialFormat::Password | SpecialFormat::Csrf => value.to_string(),
SpecialFormat::RichText => crate::utils::sanitizer::sanitize_rich(value),
_ => {
if value.contains('<') || value.contains('>') {
value.replace(['<', '>', '&'], "").trim().to_string()
} else {
crate::utils::sanitizer::sanitize_strict(value)
}
}
};
self.base.value = cleaned;
}
fn validate(&mut self) -> bool {
let mut val = self.base.value.trim().to_string();
if self.base.is_required.choice && val.is_empty() {
let msg = self
.base
.is_required
.message
.clone()
.unwrap_or_else(|| t("forms.required").to_string());
self.set_error(msg);
return false;
}
if val.is_empty() {
return true;
}
if let Some(limits) = &self.config.min_length {
let count: u32 = val.chars().count() as u32;
if count < limits.value {
let msg = limits
.message
.clone()
.unwrap_or_else(|| tf("forms.too_short", &[&limits.value]));
self.set_error(msg);
return false;
}
}
if let Some(limits) = &self.config.max_length {
let count: u32 = val.chars().count() as u32;
if count > limits.value {
let msg = limits
.message
.clone()
.unwrap_or_else(|| tf("forms.too_long", &[&limits.value]));
self.set_error(msg);
return false;
}
}
match &self.format {
SpecialFormat::Email if !val.validate_email() => {
self.set_error(t("forms.email_invalid").to_string());
return false;
}
SpecialFormat::Email => {
val = val.to_lowercase();
}
SpecialFormat::Url if !val.validate_url() => {
self.set_error(t("forms.url_invalid").to_string());
return false;
}
_ => {}
}
self.base.value = val;
self.clear_error();
true
}
fn finalize(&mut self) -> Result<(), String> {
if let SpecialFormat::Password = &self.format
&& self.hash_password
&& !self.base.value.is_empty()
{
let config = crate::utils::password::password_get();
if let PasswordConfig::Auto(_) = config {
let service = PasswordService::new(config);
if !service.is_already_hashed(&self.base.value) {
self.base.value = service
.hash(&self.base.value)
.map_err(|e| tf("forms.hash_error", &[&e.to_string()]).to_string())?;
}
}
}
Ok(())
}
fn render(&self, tera: &Arc<Tera>) -> Result<String, String> {
let mut context = Context::new();
let mut base_data = self.base.clone();
if let SpecialFormat::Password = &self.format {
base_data.value = "".to_string();
}
context.insert("field", &base_data);
context.insert("input_type", &self.base.type_field);
context.insert("readonly", &self.to_json_readonly());
context.insert("disabled", &self.to_json_disabled());
if let Some(l) = &self.config.min_length {
context.insert("min_length", &l.value);
}
if let Some(l) = &self.config.max_length {
context.insert("max_length", &l.value);
}
if let Some(rows) = self.base.extra_context.get("rows").and_then(|r| r.as_u64()) {
context.insert("rows", &rows);
}
tera.render(&self.base.template_name, &context)
.map_err(|e| {
tf(
"forms.finalize_error",
&[&self.base.template_name, &e.to_string()],
)
.to_string()
})
}
}