Здравствуйте,
Хочу написать правила для валидации форм.
Так как это частая задачу, очень хочется чтоб оно выглядело очень читабельно
А именно та часть кода где указывается условие, и название поля/причина при ошибке.
Было бы очень наглядно если бы это было как минимум в одну строку,
Собственно вся структура:
#[derive(FromForm)]
struct FormData {
name: String,
age: u32,
descr: String,
}
impl FormData {
fn validate(&self) -> Result<(), Vec<(String, String)>> {
let mut errors = Vec::new();
if self.age == 0 {
errors.push(("age".to_string(), "Zero age is not ok".to_string()));
} else if self.age > 200 {
errors.push(("age".to_string(), "Age is invalid".to_string()));
}
if self.name.len() > 255 {
errors.push(("age".to_string(), "Name is too long".to_string()));
}
if errors.len() == 0 {
Ok(())
} else {
Err(errors)
}
}
}
Первое что мне пришло в голову как можно переписать наглядней:
self.age == 0 || add_err(&mut errors, ("age".to_string(), "Zero age is not ok".to_string()));
self.age > 200 || add_err(&mut errors, ("age".to_string(), "Age is invalid".to_string()));
self.name.len() > 255 || add_err(&mut errors, ("name".to_string(), "Name is too long".to_string()));
fn add_err(errors: &mut Vec<(String, String)>, err: (String, String)) -> bool{
errors.push(err);
true
}
Но Может кто-то подскажет лучшее решение, или вообще другой подход.
P. S. Знаю что to_string() можно даже поубирать, это не суть
Ну как еще один из вариантов это убрать кучу ошибок на age. По сути одна ошибка “возраст не валиден, значение должно находится в диапазоне от 0 до 200”.
add_err переименуй в is_valid и делай макросом который будет принимать вектор картежей и будет возвращать тебе Result. Так будет не придется делать инициализацию result в каждом методе а сможешь сразу делать result is_valid(...)?
Ну а вообще к этом решению больше напрашивается макросный подход, когда на каждое поле навешивается логика валидации, как это делается в тех же шарпах
не понятно как компилятор это с оптимизирует: self.age == 0 || add_err
возможно что он вызовет add_err, при определенном условии в левой части?
Я попробовал сделать итератор полей - playground, и пусть каждое поле само себя проверяет.
собственно чтоб самому такие портянки не писать, лучше это дело замакросить. Тогда на выходе у программиста будет что то
#[derive(FromForm, ModelValid)]
struct FormData {
#[valid(MaxLength=200)]
name: String,
#[valid(Min=0, Max=200)]
age: u32,
descr: String,
}
Нужно поискать, может кто то уже делал подобные макросы… задача вроде тривиальная. Хотя я больше люблю писать руками 
Первая же ссылка в поиске. По мне так то что тебе нужно)))
Заигрывания с побочными эффектами в условиях это довольно спорная штука.
Могу еще вот такой, несколько колхозый, но безмакросный вариант предложить:
struct FormData {
name: String,
age: u32,
descr: String,
}
impl FormData {
fn validate(&self) -> Result<(), Vec<(String, String)>> {
let mut errors = Vec::new();
let mut check = |fieldname: &str, result: bool, errmsg: &str| {
if result {
errors.push((fieldname.into(), errmsg.into()));
}
};
check("age", self.age == 0, "Zero age is not ok");
check("age", self.age > 200, "Age is invalid");
check("name", self.name.len() > 255, "Name is too long");
check("descr", self.descr.len() > 255, "Description is too long");
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
fn main() {
let f = FormData {
name: "The Emperror".into(),
age: 40_000,
descr: "the Father, Guardian and God of humanity".into(),
};
println!("{:?}", f.validate());
}
Playground
Почему? Это же sequence point как и в Си - левая часть всегда вычисляется до вычисления правой. Правая не вычисляется, если левая уже true.
На этот счет много мнений есть, особенно у сишников-плюсовиков. Лично я придерживаюсь мнения, что это в лучшем случае усложняет восприятие читателем намерения автора, а в худшем читатель вообще может протупить, что есть какое-то побочное действие тут (явные &mut в этом плане иногда спасают, но не всегда).