如何建立和建立

背景知识


在Java语言开发的许多地方遇到了必须验证值的情况之后,很明显,有必要以某种方式解决此问题。 为此,设置了以下任务:
开发一个可启用以下功能的库:


  • 验证数据类型 ;
  • 设置默认值,而不是无效的字段或元素;
  • 删除对象或数组的无效部分
  • 收到错误讯息

其基础将是:


  • 易学
  • 所接收代码的可读性。
  • 易于修改代码

为了实现这些目标,已经开发了一个四方验证库。


基本验证模块


被设计为适用于各种任务的大多数系统的核心是最简单的元素 :动作,数据和算法。 以及它们的构成方法-为了从更简单的最简单的元素中组装一些东西,以解决更复杂的问题。


验证者


四方库基于验证器的概念。 该库中的验证器具有以下形式的功能


function validator( value: any, { key: string|int, parent: any }, { key: string|int, parent: any }, ... ): boolean 

此定义中有几件事应该更详细地描述:


function(...): boolean -表示验证器-计算验证结果,验证结果为布尔值-truefalse ,分别有效无效


value: any any-表示验证器-计算验证的结果,该可以是任何javascript值。 验证器将验证后的值分配给有效或无效。


{ key: string|int, parent: any }, ...表示验证值可以在不同的上下文中,具体取决于值嵌套的级别。 让我们用例子展示一下


没有上下文的示例值


 const value = 4; //         . //         : const isValueValid = validator(4) 

数组上下文中的示例值


 //  0 1 2 3 4 const arr = [1, 2, 3, value, 5] //       (k): 3 //      : [1, 2, 3, value, 5] //    value -      const isValueValid = validator(4, { key: 3, parent: [1,2,3,4,5] }) 

对象上下文中的示例值


 const obj = { a: 1, b: 2, c: value, d: 8 } //        'c' //       : { a: 1, b: 2, c: 4, d: 8 } //    value -   //   : const isValueValid = validator(4, { key: 'c', parent: { a: 1, b: 2, c: 4, d: 8 } }) 

由于对象中的结构可以具有更大的嵌套,因此谈论各种上下文是有意义


 const arrOfObj = [{ a: 1, b: 2, c: value, d: 8 }, // ... ] //   c     'c' //    : { a: 1, b: 2, c: 4, d: 8 } //        arrOfObj, //       0. //    value -      const isValueValid = validator( 4, { key: 'c', parent: { a: 1, b: 2, c: 4, d: 8 } } { key: 0, parent: [{ a: 1, b: 2, c: 4, d: 8 }] } ) 

依此类推。


关于数组方法的相似性

验证器的此定义应使您想起作为参数传递给数组方法的函数的定义,例如: map,filter,some,every等。


  • 这些函数的第一个参数是数组元素。
  • 第二个参数是元素索引。
  • 第三个参数是数组本身。

在这种情况下,验证器是一个更通用的函数-它不仅获取数组和数组中元素的索引,而且还获取其父级和父级中数组的索引,依此类推。


我们应该建什么房子?


上述砖头在javascript拐杖“海滩”上的其他“石头解决方案”中并不突出。 因此,让我们从它们中构建一些更连贯和有趣的东西。 为此,我们有一个构图


如何建立一个对象验证的摩天大楼?


同意,以使验证描述与对象描述匹配的方式来验证对象将很方便。 为此,我们将使用验证器对象组成 。 看起来像这样:


 //    quartet const quartet = require('quartet') //    (v -  validator) const v = quartet() //      , //     const objectSchema = { a: a => typeof a ==='string', //   'string' b: b => typeof b === 'number', //   'number' // ... } const compositeObjValidator = v(objectSchema) const obj = { a: 'some text', b: 2 } const isObjValid = compositeObjValidator(obj) console.log(isObjValid) // => true 

如您所见,从为特定字段定义的不同验证器块中,我们可以组装一个对象验证器-一些仍然很小的“小建筑物”,但是比没有它更好。 为此,我们使用验证器v的作曲者。 每次在验证程序的位置遇到对象文字v时,他都会将其视为对象组成,并根据其字段将其转换为对象验证程序。


有时我们无法描述所有领域 。 例如,当一个对象是数据字典时:


 const quartet = require('quartet') const v = quartet() const isStringValidator = name => typeof name === 'string' const keyValueValidator = (value, { key }) => value.length === 1 && key.length === 1 const dictionarySchema= { dictionaryName: isStringValidator, ...v.rest(keyValueValidator) } const compositeObjValidator = v(dictionarySchema) const obj = { dictionaryName: 'next letter', b: 'c', c: 'd' } const isObjValid = compositeObjValidator(obj) console.log(isObjValid) // => true const obj2 = { dictionaryName: 'next letter', b: 'a', a: 'invalid value', notValidKey: 'a' } const isObj2Valid = compositeObjValidator(obj2) console.log(isObj2Valid) // => false 

如何重用施工解决方案?


如上所述,有必要重用简单的验证器。 在这些示例中,我们已经不得不使用“字符串类型验证器”两次。


为了缩短记录并提高其可读性,四方库使用验证程序的字符串同义词。 每当验证者的作曲者在验证者应有的位置遇到字符串时,它都会在字典中搜索其验证者并使用它


默认情况下,最常见的验证器已在库中定义。


请考虑以下示例:


 v('number')(1) // => true v('number')('1') // => false v('string')('1') // => true v('string')(null) // => false v('null')(null) // => true v('object')(null) // => true v('object!')(null) // => false // ... 

以及文档中描述的许多其他问题。


每个拱门都有自己的砖头类型?


验证程序的编写者(函数v )也是验证程序的工厂。 从某种意义上说,它包含许多返回的有用方法


  • 功能验证器
  • 作曲家将其视为创建验证程序的方案的值

例如,让我们看一下数组验证:通常,它包括检查数组的类型和检查其所有元素。 我们将v.arrayOf(elementValidator)使用v.arrayOf(elementValidator)方法。 例如,采用带有名称的点数组。


  const a = [ {x: 1, y: 1, name: 'A'}, {x: 2, y: 1, name: 'B'}, {x: -1, y: 2, name: 'C'}, {x: 1, y: 3, name: 'D'}, ] 

由于点数组是对象数组,因此使用对象组合来验证数组元素是有意义的。


 const namedPointSchema = { x: 'number', // number -       y: 'number', name: 'string' // string -       } 

现在,使用工厂方法v.arrayOf ,为整个数组创建一个验证器。


 const isArrayValid = v.arrayOf({ x: 'number', y: 'number', name: 'string' }) 

让我们看看这个验证器是如何工作的:


 isArrayValid(0) // => false isArrayValid(null) // => false isArrayValid([]) // => true isArrayValid([1, 2, 3]) // => false isArrayValid([ {x: 1, y: 1, name: 'A'}, {x: 2, y: 1, name: 'B'}, {x: -1, y: 2, name: 'C'}, {x: 1, y: 3, name: 'D'}, ]) // => true 

这只是工厂方法之一,每种方法在文档中都有描述


如上所述, v.rest也是一种工厂方法,该方法返回一个对象组合,该对象组合检查未在对象组合中指定的所有字段。 这意味着可以使用spread-operator将其嵌入到另一个对象合成中。


让我们以其中的几个示例为例:


 //    quartet const quartet = require('quartet') //    (v -  validator) const v = quartet() //   ,    const max = { name: 'Maxim', sex: 'male', age: 34, status: 'grandpa', friends: [ { name: 'Dima', friendDuration: '1 year'}, { name: 'Semen', friendDuration: '3 months'} ], workExperience: 2 } //  ,   "" , // ""  , ""   -  const nameSchema = v.and( 'not-empty', 'string', //   name => name[0].toUpperCase() === name[0] // - ) const maxSchema = { name: nameSchema, //       sex: v.enum('male', 'female'), //  -   . //       "" age: v.and('non-negative', 'safe-integer'), status: v.enum('grandpa', 'non-grandpa'), friends: v.arrayOf({ name: nameSchema, //      friendDuration: v.regex(/^[1-9]\d? (years?|months?)$/) }), workExperience: v.and('non-negative', 'safe-integer') } console.log(v(maxSchema)(max)) // => true 

是还是不是?


有效数据经常采用各种形式,例如:


  • id可以是数字,也可以是字符串。
  • 根据尺寸, point对象可能包含也可能不包含某些坐标。
  • 还有许多其他情况。

为了组织变体的验证,提供了一种单独的组成类型-变体组成。 它由可能选项的验证器数组表示。 当至少一个验证者报告对象的有效性时,该对象被视为有效。


考虑一个带有标识符验证的例子:


  const isValidId = v([ v.and('not-empty', 'string'), //       v.and('positive', 'safe-integer') //    ]) isValidId('') // => false isValidId('asdba32bas321ab321adb321abds546ba98s7') // => true isValidId(0) // => false isValidId(1) // => true isValidId(1123124) // => true 

点验证示例:


 const isPointValid = v([ { //    -    x  dimension: v.enum(1), x: 'number', // v.rest    false // ,    -  ...v.rest(() => false) }, //   -    { dimension: v.enum(2), x: 'number', y: 'number', ...v.rest(() => false) }, //   - x, y  z { dimension: v.enum(3), x: 'number', y: 'number', z: 'number', ...v.rest(() => false) }, ]) // ,    ,      ,     -  -    isPointValid(1) // => false isPointValid(null) // => false isPointValid({ dimension: 1, x: 2 }) // => true isPointValid({ dimension: 1, x: 2, y: 3 //   }) // => false isPointValid({ dimension: 2, x: 2, y: 3 }) // => true isPointValid({ dimension: 3, x: 2, y: 3, z: 4 }) // => true // ... 

因此, 每当作曲家看到一个数组时,他都会认为它是此数组的验证器元素的组成,这样当其中一个认为该值有效时,验证的计算将停止并且该值将被视为有效。


如我们所见,作曲家不仅将验证器功能视为验证器,而且还将所有可能导致验证器功能的事物视为。


验证者类型例子作曲家认为
验证功能x => typeof x === 'bigint'只是要求了必要的值
对象组成{ a: 'number' }根据指定的字段验证器为对象创建验证器函数
变体成分['number', 'string']创建一个验证器函数以使用至少一个选项来验证值
工厂方法调用结果v.enum('male', 'female')大多数工厂方法都返回验证函数( v.rest ,该函数返回对象组成),因此它们被视为常规验证函数

所有这些验证器选项都是有效的,并且可以在验证器应在模式中的任何位置使用。


结果,工作方案始终是这样的: v(schema)返回验证函数。 接下来,在特定值上调用此验证函数:
v(schema)(value[, ...parents])


您在施工现场有任何事故吗?


-还没有
-他们会的!


碰巧数据无效,我们需要能够确定无效原因。


为此,四方库提供了一种解释机制。 它包含以下事实:在验证器(无论是内部验证器还是外部验证器)检测到验证数据的有效性时,必须发送说明性注释


为此, v了验证者v的作曲者的第二个参数。 它增加了在数据无效的情况下向v.explanation数组发送解释性注释v.explanation


例如,让我们验证一个数组,并想找出所有无效元素的数量及其值:


  //   -     //   const getExplanation = (value, { key: index }) => ({ invalidValue: value, index }) // ,       . //         v.explanation //    const arrValidator = v.arrayOf( v( 'number', //   getExplanation //   "",   "" ) ) // ,     ""  //     ,     //         //   ,       const explainableArrValidator = v(arrValidator, 'this array is not valid') const arr = [1, 2, 3, 4, '5', '6', 7, '8'] explainableArrValidator(arr) // => false v.explanation // [ // { invalidValue: '5', index: 4 }, // { invalidValue: '6', index: 5 }, // { invalidValue: '8', index: 7 }, // 'this array is not valid' // ] 

如您所见,解释的选择取决于任务。 有时甚至没有必要。


有时我们需要对无效字段进行处理。 在这种情况下,可以使用无效字段的名称作为解释


 const objSchema = { a: v('number', 'a'), b: v('number', 'b'), c: v('string', 'c') } const isObjValid = v(objSchema) let invalidObj = { a: 1, b: '1', c: 3 } isObjValid(invalidObj) // => false v.explanation // ['b', 'c'] //     console.error(`${v.explanation.join(', ')} is not valid`) // => b, c is not valid //       (. ) invalidObj = v.omitInvalidProps(objSchema)(invalidObj) console.log(invalidObj) // => { a: 1 } 

有了这种解释机制,您可以实现与验证结果相关的任何行为。


解释可以是任何东西:


  • 包含必要信息的对象;
  • 纠正错误的功能。 ( getExplanation => function(invalid): valid );
  • 无效字段的名称或无效元素的索引;
  • 错误代码
  • 所有这些足以让您发挥想象力。

没有构建东西时该怎么办?


纠正验证错误并非难事。 为此,该库使用带有副作用的验证器,该副作用会记住错误的位置以及如何修复错误。


  • v.default(validator, value) -返回一个记住无效值的验证程序,并在调用v.fix设置默认值
  • v.filter(validator) -返回一个记住无效值的验证器,并在调用v.fix -从父级中删除该值
  • v.addFix(validator, fixFunc) -返回一个记住无效值的验证程序,并且在调用v.fix -调用带有参数(值,{key,parent},...)的fixFunc。 fixFunc必须变异合作伙伴之一-更改值

 const toPositive = (negativeValue, { key, parent }) => { parent[key] = -negativeValue } const objSchema = { a: v.default('number', 1), b: v.filter('string', ''), c: v.default('array', []), d: v.default('number', invalidValue => Number(invalidValue)), //    pos: v.and( v.default('number', 0), //     -  0 v.addFix('non-negative', toPositive) //     -   ) } const invalidObj = { a: 1, b: 2, c: 3, d: '4', pos: -3 } v.resetExplanation() //   v() v(objSchema)(invalidObj) // => false // v.hasFixes() => true const validObj = v.fix(invalidObj) console.log(validObj) // => { a: 1, b: '', c: [], d: 4 } 

杂务仍然派上用场


该库中还有一些验证方法的实用程序方法:


方法结果
v.throwError如果无效,则使用给定的消息引发TypeError。
v.omitInvalidItems返回没有无效元素(字段)的新数组(或字典对象)。
v.omitInvalidProps根据指定的对象验证器,返回没有无效字段的新对象。
v.validOr如果有效,则返回该值,否则将其替换为指定的默认值。
v.example检查给定的值是否适合该模式。 如果它们不合适,则会引发错误。 用作文档和电路测试

结果


通过以下方式解决了任务:


挑战赛解决方案
数据类型验证默认命名验证器。
预设值v.default
移除无效零件v.filterv.omitInvalidItemsv.omitInvalidProps
易学简单的验证器,将它们组合为复杂的验证器的简单方法。
代码可读性该库的目标之一是将验证方案本身比作
验证对象。
易于修改掌握了合成的元素并使用了自己的验证功能-更改代码非常简单。
错误讯息以错误消息的形式进行说明。 或根据说明计算错误代码。

后记


该解决方案旨在快速方便地创建验证器功能,并具有嵌入自定义验证功能的能力。 因此,欢迎阅读本文的人进行任何更正,批评和改进。 谢谢您的关注。

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


All Articles