rust-参数校验宏实现

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, 一起成长.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容