美文网首页Rust 学习笔记
rust-参数校验宏实现

rust-参数校验宏实现

作者: 国服最坑开发 | 来源:发表于2020-04-17 11:48 被阅读0次

    0x00 关于参数校验


    开题: 在Spring开发中, 我们习惯了, 在参数Bean中添加JSR标准的参数校验Annotation,
    那么, 我们在rust接口开发时, 同样也会面临这样的开发需求.
    如果没有相应的开发框架, 那么接口的参数校验将会变得异常痛苦.
    经过一个星期左右的学习和实践,终于完成此篇成果.

    0x01 使用体验

    使用体验上, 与Java开发并无二致.

    #[derive(BeanCheck)]
    struct UserDO {
        #[Min(30)]
        pub min_age: u32,
        #[Max(20)]
        #[Range(1, 100)]
        pub age: u16,
        #[Length(1, 13)]
        pub username: String,
        #[Pattern(r"^\d{1,5}$")]
        pub password: String,
        #[Email]
        pub email: String,
        pub mobile: String,
    }
    
    fn main() {
        let u = UserDO {
            min_age: 325,
            age: 20,
            username: "gorey".to_string(),
            password: "12345".to_string(),
            email: "aa@qq.com".to_string(),
            mobile: "13812341234".to_string()
        };
    
        match u.validate() {
            Ok(_) => { println!("check pass "); },
            Err(e) => { println!("{}", e); },
        }
    }
    

    目前移植了数字,字符串参数的常用的校验方法.
    使用时, 只需要指定过程宏的名称, 然后就可以在相应字段上面, 添加 校验属性即可.

    0x02 原理解释


    这里使用过程宏+属性宏, 来实现需求, 首先我们需要定义一个trait, 用于宏生成的方法:

    • trait定义: bean_check_lib :
    pub trait BeanCheck {
        fn validate(&self) -> Result<()>;
    }
    

    在生在代码中, 只需要调用此方法就可以完成参数校验工作.

    • 宏实现: bean_check:
    #[proc_macro_derive(BeanCheck, attributes(NotEmpty, Length, Min, Max, Range, Email, Pattern))]
    pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
        let ast = syn::parse(input).unwrap();
        generate_validate(&ast)
    }
    

    留意此方法的宏调用:#[proc_macro_derive(BeanCheck, attributes(NotEmpty, Length, Min, Max, Range, Email, Pattern))]
    这里定义了过程宏的名称, 以及其支持的属性.

    在此方法中, 首先通过参数的TokenStream, 构建一个语法树对象ast , 然后再通过解析 ast,
    提取相应的字段及属性信息, 最后完成目标校验方法代码的生成.

    工程目录 :


    src

    0x03 开发过程宏应用, 需要知道的


    其实, 前面的帖子也聊过rust宏的基本写法, 里面提到一些基础模式匹配来提取信息.
    对于复杂代码的信息提取分类, 我们可以使用库 syn 和 quote 来帮助我们构造方法代码 .

    [dependencies]
    syn = "1.0"
    quote = "1.0"
    

    简单说明一下, syn的作用是通过解析源码(TokenStream),生成语法树对象 , 然后就可以通过面向对象的处理方式 , 进行解析, 使用十分方便.

    quote库的作用是: 构建用于生成TokenStream代码的辅助工具.
    二者结合起来, 可以高效的创造方法代码了.

    3.1 syn 说明

    从帮助理解代码的角度来说明一下syn相关知识

    • syn::DeriveInput
      最大颗粒的语法树对象 , 我们进行解析操作的数据来源

    • syn::Data
      语法树对象的 data 属性, 通过match机制, 可以匹配处理我们需要的数据类型.

    • syn::Data::Struct
      本例中, 我们只处理 struct, 所以代码中, 也仅关注此类型.

    • syn::Fields::Named
      对于 struct, 我们接下来就需要匹配struct的field信息. 那么问题来了, 有的struct可以不包含field.
      只有定义了field的场景下, 才会匹配到此类型.

    • syn::Field
      这个才是我们需要处理的对象 , 通过对上一个节点对象的field进行遍历.
      那么, 这个Field包含哪些信息呢?
      包含:

        pub struct Field {
            /// Attributes tagged on the field.
            pub attrs: Vec<Attribute>,
    
            /// Visibility of the field.
            pub vis: Visibility,
    
            /// Name of the field, if any.
            ///
            /// Fields of tuple structs have no names.
            pub ident: Option<Ident>,
    
            pub colon_token: Option<Token![:]>,
    
            /// Type of the field.
            pub ty: Type,
        }
    

    其中, 对我们有用的信息为:
    attrs : 属性列表, 也就是说, 一个字段, 可以支持多行(个)属性定义.
    ident : field变量名
    ty : field变量类型
    通过上述三种信息, 我们就可以在代码中创建校验代码了.

    举例: 这里描述的是对字符串(String)进行长度(Length)校验的例子.
    如果校验失败, 方法会返回一个自定义异常(CheckError), 用于向业务处理层反馈失败原因.

    validate_quote = quote! {
      #validate_quote
      if !( self.#ident.len() >= #min && self.#ident.len() <= #max ) {
         return Err(bean_check_lib::CheckError::Simple(stringify!(check failed: #ident #at).to_string()));
      }
    }
    

    0x04 小结


    话说回来, 与java的自定义annotation相比, 复杂度也差不多.
    然后比较苦闷的是, clion的宏, 本身就是预编译时使用的, 所以, 在开发的时候 , IDE支持性比较差,
    很多时候 还是要靠脑补.

    源码工程在 https://github.com/goreycn/api-in-rust

    欢迎+星, 提问,pull request, 一起成长.

    相关文章

      网友评论

        本文标题:rust-参数校验宏实现

        本文链接:https://www.haomeiwen.com/subject/ucxevhtx.html