Rust에서 양식 유효성 검사(Actix-Web)

양식 유효성 검사는 소프트웨어 개발 수명 주기 프로세스의 중요한 부분입니다. 양식을 작성하는 동안 정의한 데이터 유형을 기반으로 합니다. 기본적으로 몇 가지를 보장합니다.
  • 악의적인 사용자의 양식 남용을 방지합니다.
  • 스팸을 줄입니다.
  • 양식 사용자가 합법적인 데이터만 입력하도록 합니다.

  • 사용 도구 : -



  • Actix-Web : Rust를 위한 강력하고 실용적이며 매우 빠른 웹 프레임워크

  • Validator: marshmallow
    Django validators

  • lazy_static : Rust에서 느리게 평가된 통계를 선언하기 위한 매크로입니다.

  • regex : Rust용 정규식 구현.

  • 종속성



    './Cargo.toml'

    [dependencies]
    actix-web = "4"
    validator = { version = "0.12", features = ["derive"] }
    serde = { version = "1.0.104", features = ["derive"] }
    lazy_static = "1.4.0"
    regex = "1.5.6"
    


    러스티를 얻자



    Rust 프로젝트 초기화



    다음 cargo new <file-name>으로 새 프로젝트를 시작합니다.

    기본 actix 서버 구현



    actix official docs 에서 샘플 코드를 복제해 보겠습니다.

    use actix_web::{get, web, App, HttpServer, Responder};
    
    #[get("/hello/{name}")]
    async fn greet(name: web::Path<String>) -> impl Responder {
        format!("Hello {name}!")
    }
    
    #[actix_web::main] // or #[tokio::main]
    async fn main() -> std::io::Result<()> {
        HttpServer::new(|| {
            App::new()
                .route("/hello", web::get().to(|| async { "Hello World!" }))
                .service(greet)
        })
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
    }
    


    데이터를 담을 구조체 만들기



    이 프로젝트에서는 학생 모델/구조체를 만들고 있습니다.

    pub struct Student {
        pub name: String,
        pub email: String,
        pub age: u32,
        pub username: String,
        pub password: String,
    }
    


    Serde Serialize 및 Deserialize 특성 구현



    json 데이터를 구조체로 또는 그 반대로 변환합니다.

    use serde::{Deserialize, Serialize};
    
    #[derive(Serialize, Deserialize)]
    pub struct Student {
        pub name: String,
        ....
    }
    


    더 읽을 수 있습니다 here

    경로 및 핸들러 생성



    json 형태의 데이터를 받아들이고, 검증하고 검증의 성공 여부에 따라 응답을 반환하는 포스트 라우트가 될 것입니다.

    노선



    './src/main.rs

    async fn main() -> std::io::Result<()> {
        ....
        HttpServer::new(|| {
            App::new()
                .route("/", web::post().to(create_student))
        })
        ....
    }
    


    './src/main.rs

    매니저




    pub async fn create_student(json: web::Json<Student>) -> impl Responder {
        let is_valid = json.validate();
        match is_valid {
            Ok(_) => HttpResponse::Ok().json("success"),
            Err(err) => HttpResponse::BadRequest().json(err),
        }
    }
    


    여기서 let is_valid = json.validate(); 라인은 유효성 검사기를 구현합니다. 이제 정의 유효성 검사 규칙을 정의할 수 있습니다.

    유효성 검사 규칙 만들기



    validator.rs에서 Validator 특성을 파생시켜 보겠습니다.



    serde Serialize 및 Deserialize 특성과 함께 유효성 검사기 특성을 추가합니다.

    use validator::Validate;
    
    #[derive(Serialize, Deserialize, Validate)]
    pub struct Student {
        ....
    }
    


    그러나 Student Struct에 대한 요구 사항은 무엇입니까?



  • 이름 : 3자 이상이어야 합니다.

  • 이메일 : 유효한 Gmail 주소여야 합니다.

  • 나이 : 18세에서 22세 사이여야 합니다.

  • 비밀번호 : 영문 소문자 최소 1개, 영문 대문자 최소 1개, 숫자 최소 1개, 특수문자 최소 1개로 총 8자리여야 합니다. 공백이 없습니다.

  • 사용자 이름 : 영숫자와 6자 길이여야 합니다.

  • 구현 조건



  • 이름 :

    #[validate(length(min = 3,message = "Name must be greater than 3 chars"))]
    pub name: String
    

    길이 : 데이터의 길이가 정의된 길이보다 크거나 작아야 함을 보장합니다.

    message : 오류를 표시하는 클라이언트 측 메시지입니다.

  • 이메일 :

    #[validate(
        email,
        contains(pattern = "gmail", message = "Email must be valid gmail address")
    )]
    pub email: String,
    }
    

    이메일 : 이메일에 @ 기호, 도메인 이름 및 .com 및 사용자 ID와 같은 확장자와 같은 요구 사항이 포함되어 있는지 확인합니다.

    포함 및 패턴 : 특정 문자열이 특정 하위 단어(패턴)를 포함해야 하며 여기서는 gmail임을 확인합니다.

  • 나이 :

    #[validate(range(min = 18, max = 22, message = "Age must be between 18 to 22"))]
    pub age: u32,
    

    범위 : 크기가 특정 범위에 속하도록 강제합니다.

  • 사용자 이름 :

    lazy_static! {
        static ref RE_USER_NAME: Regex = Regex::new(r"^[a-zA-Z0-9]{6,}$").unwrap();
    }
    pub struct Student {
        ....
        #[validate(
            regex(
                path = "RE_USER_NAME",
                message = "Username must number and alphabets only and must be 6 characters long"
            )
        )]
        pub username: String,
        ....
    }
    

    lazy_static: Rust에서 Lazy는 정적을 평가했습니다. 벡터 또는 해시 맵 등과 같이 힙 할당이 필요한 모든 것을 포함하여 런타임에 실행됩니다.

    regex : 일치하는 경로/패턴이 필요합니다. 사용된 regex 패턴에 대한 자세한 내용은 다음을 참조하십시오.

  • 비밀번호 :

    lazy_static! {
        static ref RE_SPECIAL_CHAR: Regex = Regex::new("^.*?[@$!%*?&].*$").unwrap();
    }
    
    fn validate_password(password: &str) -> Result<(), ValidationError> {
        let mut has_whitespace = false;
        let mut has_upper = false;
        let mut has_lower = false;
        let mut has_digit = false;
    
        for c in password.chars() {
            has_whitespace |= c.is_whitespace();
            has_lower |= c.is_lowercase();
            has_upper |= c.is_uppercase();
            has_digit |= c.is_digit(10);
        }
        if !has_whitespace && has_upper && has_lower && has_digit && password.len() >= 8 {
            Ok(())
        } else {
            return Err(ValidationError::new("Password Validation Failed"));
        }
    }
    pub struct Student {
    ....
    #[validate(
        custom(
            function = "validate_password",
            message = "Must Contain At Least One Upper Case, Lower Case and Number. Dont use spaces."
        ),
        regex(
            path = "RE_SPECIAL_CHAR",
            message = "Must Contain At Least One Special Character"
        )
    )]
    pub password: String,
    ....
    }
    

    custom : 유효성 검사를 구현하는 함수가 필요하며 메시지 인수로 사용자 지정 메시지를 추가할 수도 있습니다.

  • 완전한 코드




    extern crate lazy_static;
    extern crate regex;
    extern crate serde;
    
    use actix_web::{web, App, HttpResponse, HttpServer, Responder};
    use lazy_static::lazy_static;
    use regex::Regex;
    use serde::{Deserialize, Serialize};
    use validator::{Validate, ValidationError};
    
    lazy_static! {
        static ref RE_USER_NAME: Regex = Regex::new(r"^[a-zA-Z0-9]{6,}$").unwrap();
        static ref RE_SPECIAL_CHAR: Regex = Regex::new("^.*?[@$!%*?&].*$").unwrap();
    }
    
    #[derive(Serialize, Deserialize, Validate)]
    pub struct Student {
        #[validate(length(min = 3, message = "Name must be greater than 3 chars"))]
        pub name: String,
        #[validate(
            email,
            contains(pattern = "gmail", message = "Email must be valid gmail address")
        )]
        pub email: String,
        #[validate(range(min = 18, max = 22, message = "Age must be between 18 to 22"))]
        pub age: u32,
        #[validate(
            regex(
                path = "RE_USER_NAME",
                message = "Username must number and alphabets only and must be 6 characters long"
            )
        )]
        pub username: String,
        #[validate(
            custom(
                function = "validate_password",
                message = "Must Contain At Least One Upper Case, Lower Case and Number. Dont use spaces."
            ),
            regex(
                path = "RE_SPECIAL_CHAR",
                message = "Must Contain At Least One Special Character"
            )
        )]
        pub password: String,
    }
    fn validate_password(password: &str) -> Result<(), ValidationError> {
        let mut has_whitespace = false;
        let mut has_upper = false;
        let mut has_lower = false;
        let mut has_digit = false;
    
        for c in password.chars() {
            has_whitespace |= c.is_whitespace();
            has_lower |= c.is_lowercase();
            has_upper |= c.is_uppercase();
            has_digit |= c.is_digit(10);
        }
        if !has_whitespace && has_upper && has_lower && has_digit && password.len() >= 8 {
            Ok(())
        } else {
            return Err(ValidationError::new("Password Validation Failed"));
        }
    }
    pub async fn create_student(json: web::Json<Student>) -> impl Responder {
        let is_valid = json.validate();
        match is_valid {
            Ok(_) => HttpResponse::Ok().json("success"),
            Err(err) => HttpResponse::BadRequest().json(err),
        }
    }
    
    #[actix_web::main]
    async fn main() -> std::io::Result<()> {
        println!("Starting the server at http://127.0.0.1:8080/");
        HttpServer::new(|| App::new().route("/", web::post().to(create_student)))
            .bind(("127.0.0.1", 8080))?
            .run()
            .await
    }
    
    


    확인 중



    actix 서버를 cargo run 까지 회전시켜 봅시다.

    비밀번호 확인



    유효 탑재량

    {
        "name":"prave",
        "email":"[email protected]",
        "age":19,
        "username":"praveee",
        "password":"123@4aA"
    }
    


    산출



    이름 확인



    유효 탑재량

    {
        "name":"pr",
        "email":"[email protected]",
        "age":19,
        "username":"praveee",
        "password":"123d@4aA"
    }
    


    산출



    이메일 확인



    유효 탑재량

    {
        "name":"praveen",
        "email":"[email protected]",
        "age":19,
        "username":"praveee",
        "password":"123d@4aA"
    }
    


    산출



    연령 확인



    유효 탑재량

    {
        "age": [
            {
                "code": "range",
                "message": "Age must be between 18 to 22",
                "params": {
                    "min": 18.0,
                    "value": 4,
                    "max": 22.0
                }
            }
        ]
    }
    


    산출



    사용자 이름 확인



    유효 탑재량

    {
        "name":"praveen",
        "email":"[email protected]",
        "age":19,
        "username":"prsdfdgfd@e",
        "password":"123d@4aA"
    }
    


    산출



    GitHub에서 자유롭게 질문하고 변경 사항 및 제안을 요청하십시오.

    소스 코드
    Github Repo Link

    해피해킹
    러스타시안!

    좋은 웹페이지 즐겨찾기