use tera::{Tera, Context};
use indexmap::IndexMap;
use crate::tree::Tree;
use crate::models::Statement;
use crate::translators::Rules;
pub struct FormRequestGenerator;
impl FormRequestGenerator {
pub fn generate(tree: &Tree) -> IndexMap<String, String> {
let mut files = IndexMap::new();
let mut tera = Tera::default();
tera.add_raw_template("request", REQUEST_TEMPLATE).unwrap();
let mut mod_lines = vec!["pub mod prelude;".to_string()];
let mut prelude_lines = vec![];
for controller in tree.all_controllers() {
let model_name = controller.name.trim_end_matches("Controller");
for (method_name, statements) in &controller.methods {
if !statements.iter().any(|s| matches!(s, Statement::Validate(_))) {
continue;
}
let struct_name = format!("{}{}", capitalize(model_name), capitalize(method_name));
let mut ctx = Context::new();
ctx.insert("struct_name", &struct_name);
let mut fields = vec![];
let mut validations = vec![];
for stmt in statements {
if let Statement::Validate(ref v) = stmt {
for field in &v.fields {
let rust_type = "String";
fields.push(format!(" pub {}: {},", field, rust_type));
if let Some(model) = tree.model_for_context(model_name) {
if let Some(column) = model.columns.get(field) {
let rules = Rules::from_column(column);
let val_str = rules.iter()
.map(|r| format!("\"{}\"", r))
.collect::<Vec<_>>()
.join(", ");
validations.push(format!(
" let _ = validate_length!(&body.{}, 1, 255);",
field
));
}
}
}
}
}
ctx.insert("fields", &fields);
ctx.insert("validations", &validations);
let rendered = tera.render("request", &ctx).unwrap();
let path = format!("src/requests/{}.rs", to_snake_case(&struct_name));
files.insert(path, rendered);
mod_lines.push(format!("pub mod {};", to_snake_case(&struct_name)));
prelude_lines.push(format!("pub use super::{}::{};", to_snake_case(&struct_name), struct_name));
}
}
if files.len() > 1 {
files.insert("src/requests/mod.rs".to_string(), mod_lines.join("\n"));
files.insert("src/requests/prelude.rs".to_string(), format!("//! Re-exports\n\n{}\n", prelude_lines.join("\n")));
}
files
}
}
fn to_snake_case(s: &str) -> String {
let mut result = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i > 0 { result.push('_'); }
result.push(c.to_lowercase().next().unwrap());
} else {
result.push(c);
}
}
result
}
fn capitalize(s: &str) -> String {
let mut c = s.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().to_string() + c.as_str(),
}
}
const REQUEST_TEMPLATE: &str = r#"use serde::{Deserialize, Serialize};
/// Request body for {{ struct_name }}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct {{ struct_name }} {
{% for field in fields %}
{{ field }}
{% endfor %}
}
impl {{ struct_name }} {
/// Validate the request fields.
#[allow(dead_code)]
pub fn validate(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
{% for validation in validations %}
{{ validation }}
{% endfor %}
if errors.is_empty() { Ok(()) } else { Err(errors) }
}
}
"#;