use crate::field::Widget;
use crate::form::Form;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ValidationRule {
MinLength {
field_name: String,
min: usize,
error_message: String,
},
MaxLength {
field_name: String,
max: usize,
error_message: String,
},
Pattern {
field_name: String,
pattern: String,
error_message: String,
},
MinValue {
field_name: String,
min: f64,
error_message: String,
},
MaxValue {
field_name: String,
max: f64,
error_message: String,
},
Email {
field_name: String,
error_message: String,
},
Url {
field_name: String,
error_message: String,
},
FieldsEqual {
field_names: Vec<String>,
error_message: String,
target_field: Option<String>,
},
DateRange {
start_field: String,
end_field: String,
error_message: String,
target_field: Option<String>,
},
NumericRange {
min_field: String,
max_field: String,
error_message: String,
target_field: Option<String>,
},
ValidatorRef {
field_name: String,
validator_id: String,
params: serde_json::Value,
error_message: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FormMetadata {
pub fields: Vec<FieldMetadata>,
pub initial: HashMap<String, serde_json::Value>,
pub prefix: String,
pub is_bound: bool,
pub errors: HashMap<String, Vec<String>>,
#[serde(default)]
pub validation_rules: Vec<ValidationRule>,
#[serde(default)]
pub non_field_errors: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldMetadata {
pub name: String,
pub label: Option<String>,
pub required: bool,
pub help_text: Option<String>,
pub widget: Widget,
pub initial: Option<serde_json::Value>,
}
pub trait FormExt {
fn to_metadata(&self) -> FormMetadata;
}
impl FormExt for Form {
fn to_metadata(&self) -> FormMetadata {
let fields = self
.fields()
.iter()
.map(|field| FieldMetadata {
name: field.name().to_string(),
label: field.label().map(|s| s.to_string()),
required: field.required(),
help_text: field.help_text().map(|s| s.to_string()),
widget: field.widget().clone(),
initial: field.initial().cloned(),
})
.collect();
FormMetadata {
fields,
initial: self.initial().clone(),
prefix: self.prefix().to_string(),
is_bound: self.is_bound(),
errors: self.errors().clone(),
validation_rules: self.validation_rules().to_vec(),
non_field_errors: self
.errors()
.get(crate::form::ALL_FIELDS_KEY)
.cloned()
.unwrap_or_default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fields::CharField;
#[test]
fn test_form_metadata_extraction() {
let mut form = Form::new();
form.add_field(Box::new(CharField::new("username".to_string())));
form.add_field(Box::new(CharField::new("email".to_string())));
let metadata = form.to_metadata();
assert_eq!(metadata.fields.len(), 2);
assert_eq!(metadata.fields[0].name, "username");
assert_eq!(metadata.fields[1].name, "email");
assert!(!metadata.is_bound);
}
#[test]
fn test_form_metadata_with_prefix() {
let mut form = Form::with_prefix("user".to_string());
form.add_field(Box::new(CharField::new("name".to_string())));
let metadata = form.to_metadata();
assert_eq!(metadata.prefix, "user");
assert_eq!(metadata.fields.len(), 1);
}
#[test]
fn test_form_metadata_serialization() {
let mut form = Form::new();
form.add_field(Box::new(CharField::new("test".to_string())));
let metadata = form.to_metadata();
let json = serde_json::to_string(&metadata).expect("Failed to serialize");
assert!(json.contains("\"name\":\"test\""));
let deserialized: FormMetadata =
serde_json::from_str(&json).expect("Failed to deserialize");
assert_eq!(deserialized.fields[0].name, "test");
}
#[test]
fn test_field_metadata_with_all_attributes() {
use crate::fields::CharField;
let field = CharField::new("bio".to_string())
.with_label("Biography")
.with_help_text("Tell us about yourself")
.required();
let mut form = Form::new();
form.add_field(Box::new(field));
let metadata = form.to_metadata();
let field_meta = &metadata.fields[0];
assert_eq!(field_meta.name, "bio");
assert_eq!(field_meta.label, Some("Biography".to_string()));
assert_eq!(
field_meta.help_text,
Some("Tell us about yourself".to_string())
);
assert!(field_meta.required);
}
#[test]
fn test_form_metadata_with_initial_values() {
use serde_json::json;
let mut initial = HashMap::new();
initial.insert("username".to_string(), json!("john_doe"));
initial.insert("age".to_string(), json!(25));
let mut form = Form::with_initial(initial);
form.add_field(Box::new(CharField::new("username".to_string())));
let metadata = form.to_metadata();
assert_eq!(metadata.initial.get("username"), Some(&json!("john_doe")));
assert_eq!(metadata.initial.get("age"), Some(&json!(25)));
}
#[test]
fn test_form_metadata_with_errors() {
use serde_json::json;
let mut form = Form::new();
form.add_field(Box::new(CharField::new("email".to_string()).required()));
let mut data = HashMap::new();
data.insert("email".to_string(), json!("")); form.bind(data);
let is_valid = form.is_valid();
let metadata = form.to_metadata();
assert!(!is_valid);
assert!(!metadata.errors.is_empty());
assert!(metadata.errors.contains_key("email"));
}
}