Можно ли if условия писать в одну строку или красивая валидация


#1

Здравствуйте,

Хочу написать правила для валидации форм.
Так как это частая задачу, очень хочется чтоб оно выглядело очень читабельно
А именно та часть кода где указывается условие, и название поля/причина при ошибке.
Было бы очень наглядно если бы это было как минимум в одну строку,

Собственно вся структура:

#[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() можно даже поубирать, это не суть


#2

Ну как еще один из вариантов это убрать кучу ошибок на age. По сути одна ошибка “возраст не валиден, значение должно находится в диапазоне от 0 до 200”.

add_err переименуй в is_valid и делай макросом который будет принимать вектор картежей и будет возвращать тебе Result. Так будет не придется делать инициализацию result в каждом методе а сможешь сразу делать result is_valid(...)?

Ну а вообще к этом решению больше напрашивается макросный подход, когда на каждое поле навешивается логика валидации, как это делается в тех же шарпах


#3

не понятно как компилятор это с оптимизирует: self.age == 0 || add_err
возможно что он вызовет add_err, при определенном условии в левой части?

Я попробовал сделать итератор полей - playground, и пусть каждое поле само себя проверяет.


#4

собственно чтоб самому такие портянки не писать, лучше это дело замакросить. Тогда на выходе у программиста будет что то

#[derive(FromForm, ModelValid)]
struct FormData {
    #[valid(MaxLength=200)]
    name: String,
    #[valid(Min=0, Max=200)]
    age: u32,
    descr: String,
}

Нужно поискать, может кто то уже делал подобные макросы… задача вроде тривиальная. Хотя я больше люблю писать руками :blush:


#5

Первая же ссылка в поиске. По мне так то что тебе нужно)))


#6

Заигрывания с побочными эффектами в условиях это довольно спорная штука.


Могу еще вот такой, несколько колхозый, но безмакросный вариант предложить:

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


#7

Почему? Это же sequence point как и в Си - левая часть всегда вычисляется до вычисления правой. Правая не вычисляется, если левая уже true.


#8

На этот счет много мнений есть, особенно у сишников-плюсовиков. Лично я придерживаюсь мнения, что это в лучшем случае усложняет восприятие читателем намерения автора, а в худшем читатель вообще может протупить, что есть какое-то побочное действие тут (явные &mut в этом плане иногда спасают, но не всегда).