Mengetsa data dengan travajs



Dalam posting saya sebelumnya, saya menggambarkan poin utama ketika mengembangkan perpustakaan opensource lain. Saya lupa menyebutkan satu hal lagi: jika Anda tidak memberi tahu siapa pun tentang perpustakaan, apa pun itu, kemungkinan besar tidak ada yang akan mengetahuinya.

Jadi, temui trava.js - validasi juicy untuk kepentingan proyek. Ngomong-ngomong, kami telah menggunakan rumput selama lebih dari enam bulan, dan saya pikir sudah waktunya untuk memberi tahu Anda tentang manfaat menggunakannya. Bahkan sudah kering, jadi tahan napas. Dan lanjutkan.

Konsep


Sepintas, tampaknya validasi adalah topik sepele yang tidak memerlukan perhatian khusus. Nilainya benar atau tidak, yang bisa lebih sederhana:

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

Tetapi biasanya akan menyenangkan untuk mengetahui apa yang salah:

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

Sebenarnya itu saja, masalahnya selesai.

Jika bukan karena satu "tetapi."

Dari pengalaman mengembangkan aplikasi nyata, diketahui bahwa masalah ini tidak berakhir dengan validasi. Biasanya, data ini juga perlu dikonversi ke format tertentu, karena alasan tertentu tidak didukung oleh serializer di luar kotak, misalnya tanggal, set, atau tipe data khusus lainnya. Mengingat ini terutama JSON, dalam praktiknya ternyata Anda harus melakukan double pass melalui struktur data input selama validasi dan transformasi. Idenya muncul, mengapa tidak menggabungkan dua tahap ini menjadi satu. Kemungkinan plus juga akan menjadi keberadaan skema data deklaratif eksplisit.

Untuk mendukung konversi nilai ke format tertentu, validator harus dapat mengembalikan tidak hanya kesalahan, tetapi juga nilai yang dikurangi. Di dunia js, beberapa opsi antarmuka cukup umum dengan kemungkinan pengembalian kesalahan.

  1. Mungkin yang paling umum adalah kembalinya tuple [error, data]:

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

    Ada juga opsi serupa di mana bukan array dikembalikan, tetapi objek {error, data} , tetapi tidak ada perbedaan mendasar. Keuntungan dari pendekatan ini adalah kejelasan, minusnya adalah bahwa sekarang di mana pun Anda perlu mempertahankan kontrak ini. Untuk validasi, ini tidak menyebabkan ketidaknyamanan, tetapi untuk transformasi ini jelas berlebihan.
  2. Gunakan pengecualian. Meskipun menurut saya kesalahan validasi adalah situasi standar dalam aplikasi, tidak ada yang luar biasa. Sejujurnya, saya berpikir bahwa pengecualian paling baik digunakan hanya jika ada sesuatu yang salah. Selain itu, pengecualian dapat secara tidak sengaja disebut di validator sendiri, dan kemudian Anda mungkin tidak tahu sama sekali bahwa itu adalah kesalahan dalam kode, dan bukan dalam nilainya. Keuntungan dari pendekatan ini adalah penyederhanaan antarmuka - sekarang selalu nilainya dikembalikan dengan cara biasa, dan kesalahan dilemparkan sebagai pengecualian.
  3. Ada opsi untuk menempatkan kesalahan dalam variabel global. Tapi saya tidak akan menarik negara tidak perlu.
  4. Gunakan tipe terpisah untuk kesalahan. Sepertinya opsi dengan pengecualian, jika Anda mengambil jenis kesalahan dari mereka, tetapi jangan membuangnya.

     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 } 

Saya memilih opsi yang terakhir, meskipun ini juga kompromi, tetapi secara keseluruhan tidak buruk. Trava.ValidationError diusulkan sebagai jenis kesalahan, yang mewarisi dari Kesalahan standar dan menambahkan kemampuan untuk menggunakan tipe data sewenang-wenang untuk melaporkan kesalahan. Tidak perlu menggunakan Trava.ValidationError , Anda dapat menggunakan Kesalahan standar, tetapi jangan lupa bahwa kemudian pesan kesalahan hanya string.

Untuk meringkas, kita dapat mengatakan bahwa validator adalah fungsi yang bersih dan sinkron yang, selain nilai, dapat mengembalikan kesalahan. Sangat sederhana. Dan teori ini bekerja dengan baik tanpa perpustakaan. Dalam praktiknya, validator digabungkan ke dalam rantai dan hierarki, dan di sini rumput pasti akan berguna.

Komposisi


Mungkin komposisi adalah kasus paling umum bekerja dengan validator. Implementasi komposisi mungkin berbeda. Misalnya, di pustaka joi dan v8n yang terkenal , ini dilakukan melalui objek dan serangkaian metode:

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

Meskipun terlihat indah pada pandangan pertama, pendekatan ini memiliki beberapa kelemahan, dan satu fatal. Dan inilah masalahnya. Dalam pengalaman saya, validator selalu menjadi hal untuk aplikasi spesifik, jadi fokus utama di perpustakaan harus pada kenyamanan memperluas validator dan integrasi dengan pendekatan yang ada, dan bukan pada jumlah primitif dasar, yang, menurut saya, hanya menambah bobot ke perpustakaan, tetapi sebagian besar tidak akan digunakan. Ambil contoh validator yang sama untuk string. Kemudian ternyata Anda perlu memangkas spasi dari ujungnya, lalu tiba-tiba Anda perlu mengizinkan penggunaan karakter khusus dalam satu kasus tunggal, dan di suatu tempat Anda perlu mengarah ke huruf kecil, dll. Bahkan, mungkin ada banyak primitif seperti itu, dan saya hanya tidak melihat gunanya bahkan mulai menambahkannya ke perpustakaan. Menurut pendapat saya, penggunaan objek juga berlebihan dan mengarah pada peningkatan kompleksitas selama ekspansi, meskipun pada pandangan pertama tampaknya membuat hidup lebih mudah. Misalnya, c joi tidak begitu mudah untuk menulis validator Anda .

Pendekatan fungsional dan rumput di sini dapat membantu. Contoh yang sama untuk memvalidasi angka yang ditentukan dalam rentang dari 0 hingga 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); // ! 

Pernyataan Periksa membuat validator keluar dari pemeriksaan kebenaran (nilai => benar / salah). Dan menulis rantai validator. Ketika dieksekusi, rantai terputus setelah kesalahan pertama. Yang penting adalah bahwa fungsi-fungsi biasa digunakan di mana-mana, yang sangat mudah untuk diperluas dan digunakan. Menurut saya, kemudahan ekspansi ini adalah fitur kunci dari pustaka validasi yang valid.

Secara tradisional, tempat terpisah dalam validasi ditempati dengan memeriksa nol dan tidak terdefinisi . Ada operator pembantu di rumput untuk ini:

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

Ada beberapa operator pembantu lagi di rumput, dan mereka semua menyusun dengan indah dan mengejutkan hanya berkembang. Seperti fungsi biasa :)

Hierarki


Tipe data sederhana disusun dalam hierarki. Kasing yang paling umum adalah objek dan array. Ada operator di rumput yang membuatnya lebih mudah untuk bekerja dengan mereka:

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

Ketika memvalidasi objek, diputuskan untuk menekankan keparahan definisi: semua kunci diperlukan secara default (dibungkus dalam Wajib ). Kunci yang tidak ditentukan dalam validator dibuang.

Beberapa jsonschema , solusi kuartet lebih suka menggambarkan validator dalam bentuk data, misalnya {x: 'number', y: 'number'}, tetapi ini mengarah pada kesulitan yang sama ketika berkembang. Keuntungan yang signifikan dari pendekatan ini adalah kemungkinan serialisasi dan pertukaran sirkuit, yang tidak mungkin dengan fungsi. Namun, ini dapat dengan mudah diimplementasikan di atas antarmuka fungsional. Tidak perlu menyembunyikan fungsi di belakang garis! Fungsi sudah memiliki nama dan hanya itu yang diperlukan.

Untuk kemudahan penggunaan di dalam validator, operator Compose dan Keys dapat dihilangkan, juga nyaman untuk membungkus validator root di Trava :

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

Jika Anda memanggil Trava dengan argumen kedua, maka nilai balik akan menjadi hasil penerapan validator:

 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 } 

Sejauh ini, dukungan telah diterapkan hanya untuk array dan objek, seperti pada dasarnya meracuni JSON dan itu sudah cukup. Tarik Permintaan untuk Wellcome!

Konteks


Saat menggunakan validator sebagai parameter terakhir, Anda dapat melewati konteks yang akan dapat diakses dari semua validator yang disebut sebagai parameter terakhir. Secara pribadi, kesempatan ini belum berguna bagi saya, tetapi itu mungkin.

Untuk beberapa validator yang mungkin mengembalikan kesalahan, dimungkinkan untuk menentukan pesan kesalahan di tingkat yang berbeda. Contoh:

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

Ganti untuk satu kasus:

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

Ganti untuk semua kasus:

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

Selain itu, untuk konfigurasi yang lebih terperinci, Anda dapat mentransfer fungsi di tempat kesalahan, yang seharusnya mengembalikan kesalahan dan akan dipanggil dengan parameter validator.

Gunakan kasing


Sebagian besar kita meracuni JSON di backend bersama dengan koa. Frontend juga duduk perlahan. Lebih mudah memiliki validator bersama di kedua ujungnya. Dan sekarang saya akan menunjukkan kasus penggunaan yang hampir nyata. Misalkan Anda ingin menerapkan API untuk membuat dan memperbarui data pasien.

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

common / errors.js
const trava = membutuhkan ('trava');

function ValidationError (ctx, params) {
if (params instance of Error) {
params = trava.ValidationError.extractData (params);
}
ctx.body = {
kode: 'VALIDATION_ERROR',
params,
};
ctx.status = HttpStatus.BAD_REQUEST;
}

Meskipun contohnya sangat sederhana, itu tidak bisa disebut disederhanakan. Dalam aplikasi nyata, hanya validator yang akan rumit. Anda juga dapat membuat validasi di middleware - validator diterapkan sepenuhnya pada konteks atau ke badan permintaan.

Dalam proses bekerja dan menggunakan validasi, kami sampai pada kesimpulan bahwa validator sinkron sederhana dan pesan kesalahan sederhana sudah cukup. Bahkan, kami sampai pada kesimpulan bahwa kami hanya menggunakan dua pesan: "DIBUTUHKAN" dan "INVALID", yang dilokalkan di frontend bersama dengan prompt untuk bidang. Pemeriksaan lain yang memerlukan tindakan tambahan (misalnya, pada saat pendaftaran untuk memeriksa apakah surat semacam itu sudah ada) berada di luar ruang lingkup validasi. Bagaimanapun, rumput bukan tentang hal ini.

Kesimpulannya


Dalam artikel singkat ini, saya menjelaskan hampir seluruh fungsi perpustakaan, di luar ruang lingkup artikel ada beberapa pembantu yang menyederhanakan kehidupan. Saya meminta detail di github github.com/uNmAnNeR/travajs .

Kami membutuhkan alat yang dapat disesuaikan sebanyak mungkin, di mana tidak ada yang berlebihan, tetapi pada saat yang sama ada semua yang diperlukan untuk pekerjaan sehari-hari. Dan saya pikir secara umum ini tercapai, saya berharap seseorang juga akan membuat hidup lebih mudah. Saya akan senang dengan keinginan dan saran.
Untuk kesehatan.

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


All Articles