ビルドとビルドの方法

背景


Javascript開発の多くの場所で、値を検証する必要がある状況に遭遇したため、この問題を何らかの形で解決する必要があることが明らかになりました。 この目的のために、次のタスクが設定されました。
以下を可能にするライブラリを開発します。



その根拠は次のとおりです。



これらの目標を達成するために、 カルテット検証ライブラリが開発されました。


基本的な検証ブロック


幅広いタスクに適用できるように設計されているほとんどのシステムの中心にあるのは、アクション、データ、アルゴリズムなどの最も単純な要素です。 同様に、それらの構成の方法-より複雑な問題を解決するために、より複雑な最も単純な要素から何かを組み立てるために。


バリデーター


カルテットライブラリは、 バリデータの概念に基づいています。 このライブラリのバリデータは次の形式の関数です


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

この定義には、さらに詳しく説明する必要のあるものがいくつかあります。


function(...): booleanバリデーター-検証の結果を計算し、検証の結果はブール値-trueまたはfalse 、それぞれ有効または無効


value: any any-バリデーターがvalueの検証結果を計算することを示します。これは任意の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などの配列メソッドに引数として渡される関数の定義を思い出させる必要があります。


  • これらの関数の最初の引数は配列要素です。
  • 2番目の引数は、要素インデックスです。
  • 3番目の引数は配列自体です。

この場合のバリデーターはより一般化された関数です-配列内の要素のインデックスと配列だけでなく、配列のインデックスも取得します-その親とその親などで。


家を建てるのは何ですか?


上記のレンガは、javascript crutch 「beach」の上に横たわる他の「石の解決策」の中でも際立っていません。 したがって、より一貫性のある興味深いものから構築しましょう。 このために、 構成があります。


オブジェクト検証の超高層ビルを構築する方法は?


同意します。検証の説明がオブジェクトの説明と一致するようにオブジェクトを検証すると便利です。 このために、 validatorsのオブジェクト構成を使用します 。 次のようになります。


 //    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 

建築ソリューションを再利用する方法は?


上で見たように、単純なバリデーターを再利用する必要があります。 これらの例では、すでに「string type validator」を2回使用する必要がありました。


レコードを短くして読みやすくするために、カルテットライブラリはバリデータの文字列シノニムを使用します。 バリデーターの作曲者は、バリデーターがあるべき場所で文字列に遭遇するたびに、辞書でバリデーターを探して使用します。


デフォルトでは、最も一般的なバリデーターはすでにライブラリーに定義されています。


以下の例を検討してください。


 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)メソッドを使用します。 たとえば、名前を持つポイントの配列を取得します。


  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 

これはファクトリーメソッドの1つにすぎません。各メソッドはドキュメントに記載されています。


上で見たように、 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 

あるべきかどうか


有効なデータは、たとえば次のようなさまざまな形式をとることがよくあります。



オプションの検証を整理するために、別のタイプの構成-バリアント構成が提供されます。 可能なオプションのバリデーターの配列で表されます。 バリデータの少なくとも1つが有効性を報告した場合、オブジェクトは有効であると見なされます。


識別子の検証の例を考えてみましょう。


  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 // ... 

したがって、 作曲家が配列を見るときはいつでも、その配列のバリデーター要素の構成とみなし、その中の1つが値を有効と見なすと、検証の計算が停止し、値が有効であると認識されます。


ご覧のとおり、コンポーザはバリデータ関数をバリデータとしてだけでなく、バ​​リデータ関数につながる可能性のあるすべてのものも考慮します。


検証タイプ作曲家が知覚したとおり
検証機能x => typeof x === 'bigint'必要な値を呼び出しただけです
オブジェクト構成{ a: 'number' }指定されたフィールドバリデータに基づいてオブジェクトのバリデータ関数を作成します
バリアント構成['number', 'string']少なくとも1つのオプションで値を検証するための検証関数を作成します
ファクトリメソッド呼び出しの結果v.enum('male', 'female')ほとんどのファクトリメソッドは検証関数を返します(オブジェクト構成を返すv.rest除く)ので、それらは通常の検証関数のように扱われます

これらの検証オプションはすべて有効であり、検証ツールがあるスキーマ内のどこででも使用できます。


その結果、操作スキームは常に次のようになりv(schema)は検証関数を返します。 次に、特定の値でこの検証関数が呼び出されます。
v(schema)(value[, ...parents])


建設現場で何か事故がありましたか?


-まだ誰もいない
-彼らはします!


そのため、データが無効であり、無効の原因を特定できる必要があります。


このため、カルテットライブラリは説明メカニズムを提供します。 それは、内部または外部にかかわらず、バリデーターが検証済みデータの有効性を検出した場合に、 注釈を送信する必要があるという事実にあります。


これらの目的のために、バリデーターコンポーザーv 2番目の引数vます。 無効なデータの場合に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 } 

この説明のメカニズムを使用して、検証の結果に関連付けられた任意の動作を実装できます。


何でも説明できます:



物事が構築されていない場合の対処


検証エラーを修正することはめったにありません。 これらの目的のために、ライブラリは、エラーの場所と修正方法を記憶する副作用を持つバリデータを使用します。



 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.omitInvalidItemsおよびv.omitInvalidProps
習得が簡単シンプルなバリデーター、それらを複雑なバリデーターに合成する簡単な方法。
コードの読みやすさライブラリの目標の1つは、検証スキーム自体を類推することでした
検証済みオブジェクト。
変更の容易さコンポジションの要素を習得し、独自の検証機能を使用して-コードの変更は非常に簡単です。
エラーメッセージエラーメッセージとしての説明。 または、説明に基づいたエラーコードの計算。

あとがき


このソリューションは、カスタム検証関数を埋め込む機能を備えた検証関数を迅速かつ便利に作成するように設計されました。 したがって、もしあれば、この記事を読んだ人からの修正、批判、改善オプションは歓迎です。 ご清聴ありがとうございました。



Source: https://habr.com/ru/post/J429916/


All Articles