使用travajs蚀刻数据



在我以前的文章中,我描述了开发另一个开源库时的要点。 我忘了再提一件事:如果您不告诉任何人关于图书馆的信息,无论它是什么,很可能没人会知道。

因此,请访问trava.js-多汁验证,以使项目受益。 顺便说一句,我们使用草已有六个多月了,我认为是时候告诉您使用草的好处了。 甚至已经干了,所以屏住呼吸。 继续吧

概念图


乍一看,验证似乎是一个琐碎的主题,不需要特别注意。 该值是否为true,可能更简单:

function validate (value) { // any checking... if (!check(value)) return false; return true; } 

但是通常最好知道到底出了什么问题:

 function validate (value) { if (!check1(value)) return 'ERROR_1'; if (!check2(value)) return 'ERROR_2'; } 

其实仅此而已,问题就解决了。

如果不是一个“但是”。

从开发实际应用程序的经验来看,注意到问题并没有以验证结束。 通常,由于序列化程序不支持的某些原因(例如日期,集合或其他自定义数据类型),此数据也需要转换为特定格式。 鉴于这主要是JSON,实际上,您需要在验证和转换过程中对输入数据结构进行两次遍历。 这个想法出现了,为什么不将这两个阶段合而为一呢。 一个可能的优点是显式声明性数据模式的存在。

为了支持将值转换为特定格式,验证器必须不仅能够返回错误,而且还可以返回减小的值。 在js世界中,相当多的接口选项很常见,可能会返回错误。

  1. 可能最常见的是[错误,数据]元组的返回:

     function validate (value) { if (!check1(value)) return ['ERROR_1']; if (!check2(value)) return ['ERROR_2']; return [null, value]; } 

    还有一个类似的选项,其中不返回数组,而是返回{error,data}对象,但没有根本区别。 这种方法的优点是显而易见,而缺点是现在您需要在任何地方维护此合同。 对于验证,这不会造成不便,但是对于转换来说,这显然是多余的。
  2. 使用例外。 尽管我认为验证错误是应用程序中的标准情况,但没有什么例外。 老实说,我认为只有在真正出错的地方才最好使用例外。 另外,在验证器中可能会意外地调用异常,然后您可能根本不知道这是代码中的错误,而不是值中的错误。 该方法的优点是简化了接口-现在总是以通常的方式返回值,并且将错误作为异常抛出。
  3. 有一个选项可以将错误放入全局变量中。 但是我不会不必要地拉扯国家。
  4. 为错误使用单独的类型。 如果您从错误中删除错误类型,但不要将其丢弃,它看起来就像带有异常的选项。

     function validate (value) { if (!check1(value)) return new Trava.ValidationError({ code: 401 }); if (!check2(value)) return new Trava.ValidationError({ code: 405 }); return parseOrTransform(value); // apply some parse or transform } 

我选择了后者,尽管这也是一个折衷方案,但总体来说还不错。 建议将Trava.ValidationError作为错误的一种类型,它从标准Error继承而来,并增加了使用任意数据类型报告错误的功能。 不必使用Trava.ValidationError ,可以使用标准的Error ,但是请不要忘记,错误消息只是字符串。

总而言之,我们可以说验证器是一个干净的同步函数,除该值外,还可以返回错误。 非常简单。 这种理论在没有库的情况下也能很好地工作。 在实践中,验证器被组合成链和层次结构,在这里,草肯定会派上用场。

组成


也许组合是与验证者一起工作的最常见情况。 组成的实现方式可能不同。 例如,在著名的joiv8n库中,这是通过对象和方法链来完成的:

 Joi.string().alphanum().min(0).max(255) 

尽管乍看起来很漂亮,但是这种方法有几个缺点,一个是致命的。 这就是事情。 以我的经验,验证器始终是特定应用程序的事情,因此库中的主要重点应该放在扩展验证器和与现有方法集成的便利性上,而不是基本原语的数量上,我认为这仅会增加库的权重,但是大多数将不会使用。 例如,使用相同的字符串验证器。 事实证明,您需要从两端开始修剪空格,然后突然需要在一种情况下允许使用特殊字符,而在某些情况下则需要使用小写字母,等等。 实际上,可以有无限多个这样的原语,我什至看不到开始将它们添加到库中的意义。 我认为,对象的使用也是多余的,并且会导致扩展过程中的复杂性增加,尽管乍一看似乎可以使生活更轻松。 例如,c joi 编写验证器并不容易。

功能性方法和草在这里可能会有所帮助。 验证在0到255范围内指定的数字的相同示例:

 //    const isNumber = n => typeof n == 'number' && !isNaN(n); //  const numberValidator = Trava.Check(isNumber); const byteValidator = Trava.Compose([ numberValidator, Trava.Check(n => 0 <= n && n < 256), ]); byteValidator(-1); // ! 

Check语句使验证器脱离真伪检查(值=> true / false)。 并且Compose链接验证器。 执行时,链在第一个错误之后中断。 重要的是,普通功能随处可见,它们的扩展和使用非常简单。 我认为,这种扩展的简便性是有效的验证库的关键功能。

传统上,验证中的单独位置是通过检查nullundefined来占据的。 在草丛中有辅助运算符:

 //   null  undefined const requiredNumberValidator = Trava.Required(numberValidator); requiredNumberValidator(undefined); // ! const optNumberValidator = Trava.Optional(numberValidator, 2); // 2 is default optNumberValidator(undefined); // 2 optNumberValidator(null); // null const nullNumberValidator = Trava.Nullable(numberValidator, 3); // 3 is default nullNumberValidator(undefined); // 3 nullNumberValidator(null); // 3 

在草丛中还有更多的辅助运算符,它们的组合精美且令人惊讶地只是简单地扩展。 像普通功能一样:)

层次结构


简单数据类型被组织成一个层次结构。 最常见的情况是对象和数组。 草丛中有一些操作员,可以更轻松地与他们合作:

 //   const byteArrayValidator = Trava.Each(byteValidator); byteArrayValidator([1, -1, 2, -3]); // ValidationError: {"1":"Incorrect value","3":"Incorrect value"} //   const pointValidator = Trava.Keys({ x: numberValidator, y: numberValidator, }); pointValidator({x: 1, y: 'a'}); // ValidationError: {"y":"Incorrect value"} 

在验证对象时,决定强调定义的严重性:默认情况下,所有键都是必需的(包装在Required中 )。 验证器中未指定的密钥将被丢弃。

一些jsonschema 四方解决方案更喜欢以数据形式描述验证器,例如{x:'number',y:'number'},但这在扩展时会带来同样的困难。 这种方法的显着优点是可以实现电路的串行化和交换,而这对于功能而言是不可能的。 但是,这可以在功能接口之上轻松实现。 无需隐藏任何功能! 函数已经有名称,仅此而已。

为了方便在验证器中使用,可以省略ComposeKeys运算符;将根验证器包装在Trava中也很方便:

 const pointValidator = Trava({ //  -> Keys x: [numberValidator, Trava.Check(v => v > 180)], //  -> Compose y: [numberValidator, Trava.Check(v => v < 180)], }); 

如果使用第二个参数调用Trava ,则返回值将是应用验证器的结果:

 const point = Trava({ x: [numberValidator, Trava.Check(v => v > 180)], y: [numberValidator, Trava.Check(v => v < 180)], }, //      { x: 200, y: 100, }); // { x: 200, y: 100 } 

到目前为止,仅对数组和对象实施了支持,因为 基本上会毒化JSON就足够了 拉惠康的要求!

语境


将验证器用作最后一个参数时,您可以将可从所有调用的验证器访问的上下文作为最后一个参数。 就个人而言,这个机会对我来说还没有派上用场,但是有可能。

对于某些可能返回错误的验证器,可以在不同级别定义错误消息。 一个例子:

 const pos = Trava.Check(v => v > 0); pos(-1); // ValidationError: "Incorrect value" (by default) 

覆盖单个案例:

 const pos = Trava.Check(v => v > 0, "    "); pos(-1); // ValidationError: "    " 

覆盖所有情况:

 Trava.Check.ErrorMessage = " "; pos(-1); // ValidationError: " " 

另外,对于更详细的配置,您可以在错误的地方转移一个函数,该函数应该返回一个错误,并使用验证器参数进行调用。

用例


通常,我们会在后端将JSON和koa毒化。 前端也慢慢坐起来。 在两端都有通用的验证器很方便。 现在,我将展示一个几乎真实的用例。 假设您要实现一个API以创建和更新患者数据。

 // validators.js const trava = require('trava'); const { isFilledString, isDate, isNumber } = require('../common/validators'); const patientSchema = { name: isFilledString, dateOfBirth: isDate, height: isNumber, } //        //      const patientNew = trava(patientSchema); //      const patientPatch = trava(mapValues(patientSchema, trava.Optional)); module.exports = { patientNew, patientPatch, }; // controllers.js const validate = require('./validators'); const { ValidationError } = require('../common/errors'); function create (ctx) { const patientData = validate.patientNew(ctx.request.body); //       Error,             Error if (patientData instanceof Error) return ValidationError(ctx, patientData); // ...create new patient } function update (ctx) { const patientData = validate.patientPatch(ctx.request.body); if (patientData instanceof Error) return ValidationError(ctx, patientData); // ...update patient data } 

常见的/ errors.js
const trava = require('trava');

函数ValidationError(ctx,params){
if(params instanceof Error){
params = trava.ValidationError.extractData(params);
}
ctx.body = {
代码:“ VALIDATION_ERROR”,
参数,
};
ctx.status = HttpStatus.BAD_REQUEST;
}

尽管该示例非常简单,但不能称之为简化。 在实际应用中,只有验证器会很复杂。 您还可以在中间件中进行验证-验证程序完全应用于上下文或请求正文。

在工作和使用验证的过程中,我们得出的结论是,简单的同步验证器和简单的错误消息就足够了。 实际上,我们得出的结论是,我们仅使用了两条消息:“ REQUIRED”和“ INVALID”,这些消息与字段提示一起位于前端。 其他需要其他操作的检查(例如,在注册时检查此类邮件是否已存在)不在验证范围内。 无论如何,草不是这种情况。

总结


在这篇简短的文章中,我描述了该库的几乎所有功能,但在本文范围之外,还有一些助手可以简化生活。 我在github github.com/uNmAnNeR/travajs上询问详细信息。

我们需要一个尽可能定制的工具,其中没有多余的东西,但同时又有日常工作所需的一切。 而且我认为总体上已经实现了这一目标,我希望有人也可以使生活更轻松。 我将很高兴有希望和建议。
为了健康。

Source: https://habr.com/ru/post/zh-CN462189/


All Articles