3 个版本

使用旧的 Rust 2015

0.2.2 2017 年 9 月 1 日
0.2.1 2017 年 9 月 1 日
0.2.0 2017 年 9 月 1 日

9#input-validation

Apache-2.0/MIT

25KB
188

检测器

a•SEY•er, n.

  1. 分析物质的详细发现报告
  2. 一个不幸的人,他的工作是品尝皇家食物,以防食物中有毒
  3. 静态(编译时)验证库,用于 Rust

动机

通常,程序需要处理不受信任的输入。在这样做之前,确保正在处理的数据值确实适用于它们要完成的目标是很重要的。

例如,包括表示一天中的小时、月份中的日期、人的年龄、URL、电话号码等的数据值。无论底层数据类型(整数、字符串、自定义结构体等)如何,有些值是有效的,而有些则不是。

作为一个验证库,检测器允许您通过将自定义验证逻辑集中在单一位置——您的验证器——来构建高效、高度内聚的代码。您可以为需要验证的应用程序中的不同类型创建任意数量的验证器。

如何使用

快速入门

  • 通过实现 ValidatorRef 和/或 ValidatorMutRef 和/或 Validator 来定义您的自定义验证器(例如,用于非空字符串),具体取决于您想要的移动/复制/可变性语义。以下是一个使用 ValidatorRef 的示例
extern crate assayer;
use assayer::{ValidatorRef, ValidatorMutRef, Validator};  //Ok to import just the one(s) you want to use
use assayer::{MethodSyntaxValidatorRef, MethodSyntaxValidatorMutRef, MethodSyntaxValidator}; //ditto
use assayer::Error as AssayerError;

struct NonEmptyString;  //A dummy custom type to validate

impl ValidatorRef<String> for NonEmptyString {
    fn validate_ref(input: &String) -> std::result::Result<&String, AssayerError> {
        Ok(input)
            .and_then(|input| match !input.is_empty() {
                true => Ok(input),
                false => Err(AssayerError::ValueEmpty("<Validation failure message here>".to_string())),
            })

        // Chain as many additional .and_then() clauses as you need to validate your input.
        // Each validation clause in the .and_then() chain must return:
        //   * Ok(input) if the value passes that validation step or
        //   * Err(String) if the value fails that validation step.
        // A clause in a Result::and_then() chain which returns Err() will terminate further validation processing
        // and the returned Err() will be returned to the caller.

        //.and_then(|input| ...test #2...)
        //...
        //.and_then(|input| ...test #n...)
    }
}

要调用验证器,请使用下面的函数或方法调用语法

    //Function-call syntax example
    let empty_string = String::new();
    let function_call_syntax_result = String::validate_ref::<NonEmptyString>(&empty_string);
    assert_eq!(function_call_syntax_result.err(),
               Some(AssayerError::ValueEmpty("<Validation failure message here>".to_string())));

    //Method-call syntax example
    let non_empty_string = "Not an empty string".to_string();
    let method_call_syntax_result = (&non_empty_string).validate_ref::<NonEmptyString>();
    assert_eq!(method_call_syntax_result.ok(), Some(&non_empty_string));

使用说明:上面的只是一个快速示例,说明如何设置并调用库。在实际应用中,您可以让您的自定义类型构造函数在输入参数上调用验证器,这样一旦类型成功构造,就可以提供强大的保证,即其内容始终有效。

常见问题解答

检测器的一些设计目标是什么?

• 轻量级,无依赖

语言长期以来一直朝着引入其他“模块”并在其之上构建的方向发展(JavaScript 社区在这方面尤其突出)。Rust 的 Cargo 和 crate 系统为 Rust 带来了方便的依赖性,我们完全欢迎这一点。同时,我们是美丽、可维护、可预测、小而专注的代码的粉丝。当更少的代码可以完成更多的工作时,我们认为这种效率提升尤其优雅。因此,库最终只包含六个特质定义、三个泛型实现和没有依赖。

• 促进高度内聚、单一责任和不要重复原则

验证器允许您将所有检查因素化到前置条件中,所有这些都集中在同一个地方。当一个程序中的所有代码都专注于实现同一个目标时,您就会得到高度内聚,这已经被证明对代码库的可靠性和可维护性非常有益。

通过将验证代码集中在验证器中,验证器只需负责一件事:验证。单一职责原则有助于防止代码膨胀成“全能对象”,如果你曾经有幸与之合作,我们相信你一定会同意你不想很快再次回到它身边!

• 静态(编译时)验证器

作为一个静态验证器,你创建的验证链能够充分利用编译器的优化器。在运行时使用例如构建器模式的动态验证器无法实现这些优势,因为验证链在编译时并不存在作为链。

我们考虑了各种用例,但想不出静态验证器的缺点。如果你发现了动态验证器的有力论据,请告诉我们!

函数调用语法和方法调用语法的区别是什么?

从代码角度来看,没有区别。(对于我们针对Rust v1.18.0进行的测试,编译器生成的汇编指令100%相同。)

从个人角度来看,你可能更习惯于一种风格,或者在某些特定情况下使用一种特定的风格可能更方便。选择权在你。

我应该何时实现ValidatorValidatorRefValidatorMutRef

实现Validator意味着你的验证器将消费调用者传递的值

impl Validator<String> for NonEmptyString {
    fn validate(input: String) -> std::result::Result<String, AssayerError> {
        Ok(input)
            .and_then(|input| match !input.is_empty() {
                true => Ok(input),
                false => Err(AssayerError::ValueEmpty("<Validation failure message here>".to_string())),
            })
    }
}

fn main() {
    let input = "Not an empty string".to_string();
    let result = input.validate::<NonEmptyString>();
    assert_eq!(result.ok(), Some(input)); //Oops!  input has been moved!
}

如果这不可取,a) 调用者可以调用验证器时使用clone():input.clone().validate::<NonEmptyString>(),或者b) 你可以选择实现ValidateRef,调用者可以调用验证器如下:(&input).validate_ref::<NonEmptyString>()

实现ValidatorMutRef的情况相对较少,并且可能违反单一职责原则。无论你是否认为它违反了,它可以用作在验证后代码使用之前‘修正’传入的值(例如,你可能想在使用之前填充或四舍五入有效的输入值)。以下是它的使用方法

impl ValidatorMutRef<String> for NonEmptyString {
    fn validate_mut_ref(input: &mut String) -> std::result::Result<&mut String, AssayerError> {
        Ok(input)
            .and_then(|input| match !input.is_empty() {
                true => { input.push_str("!"); Ok(input) }, //proof validate_mut_ref can mutate the input value
                false => Err(AssayerError::ValueEmpty("<Validation failure message here>".to_string())),
            })
    }
}

fn main() {
    let mut input = "Not an empty string".to_string();
    let mut expected_result_inner = "Not an empty string!".to_string();
    let expected_result = Some(&mut expected_result_inner);

    let result = (&mut input).validate_mut_ref::<NonEmptyString>();
    assert_eq!(result.ok(), expected_result);
}

最后,如果你想简单地使三种实现中的任何一种“正常工作”,根据你的需求实现ValidateRef,并按照以下方式实现ValidateMutRef和Validate的“空白实现”

impl ValidatorMutRef<String> for NonEmptyString {
    fn validate_mut_ref(input: &mut String) -> Result<&mut String> {
        Ok(input)
            .and_then(|input| match (&*input).validate_ref::<NonEmptyString>() {
                Ok(_) => Ok(input),
                Err(err) => Err(err),
            })
    }
}

impl Validator<String> for NonEmptyString {
    fn validate(input: String) -> Result<String> {
        Ok(input)
            .and_then(|input| match (&input).validate_ref::<NonEmptyString>() {
                Ok(_) => Ok(input),
                Err(err) => Err(err),
            })
    }
}

无运行时依赖