Jelajahi Tipe Literal Template TypeScript dan bangun mesin validasi runtime untuk verifikasi string yang kuat dan keamanan tipe. Pelajari cara mencegah kesalahan dengan memvalidasi string terhadap tipe literal template yang Anda tentukan saat runtime.
Mesin Validasi Template Literal TypeScript: Verifikasi String Runtime
Tipe literal template TypeScript menawarkan manipulasi string dan keamanan tipe yang kuat pada saat kompilasi. Namun, pemeriksaan ini terbatas pada saat kompilasi. Posting blog ini mengeksplorasi cara membangun mesin validasi runtime untuk tipe literal template TypeScript, memungkinkan verifikasi string yang kuat dan mencegah potensi kesalahan selama eksekusi program.
Pengantar Tipe Literal Template TypeScript
Tipe literal template memungkinkan Anda mendefinisikan bentuk string tertentu berdasarkan nilai literal, gabungan (union), dan inferensi tipe. Ini memungkinkan pemeriksaan tipe yang tepat dan pelengkapan otomatis, yang sangat berguna saat berhadapan dengan data terstruktur atau bahasa spesifik domain.
Misalnya, pertimbangkan tipe untuk mewakili kode mata uang:
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const validCurrency: FormattedCurrencyString = "USD-100"; // OK
const invalidCurrency: FormattedCurrencyString = "CAD-50"; // Kesalahan tipe saat kompilasi
Contoh ini mendemonstrasikan bagaimana TypeScript memberlakukan tipe FormattedCurrencyString pada saat kompilasi. Namun, jika kode mata uang berasal dari sumber eksternal (misalnya, input pengguna, respons API), Anda memerlukan validasi runtime untuk memastikan keamanan tipe.
Kebutuhan akan Validasi Runtime
Meskipun TypeScript menyediakan pemeriksaan tipe yang sangat baik pada saat kompilasi, ia tidak dapat menjamin validitas data yang diterima dari sumber eksternal pada saat runtime. Mengandalkan hanya pada tipe saat kompilasi dapat menyebabkan kesalahan dan kerentanan yang tidak terduga.
Pertimbangkan skenario berikut:
function processCurrency(currencyString: FormattedCurrencyString) {
// ... beberapa logika yang mengasumsikan string diformat dengan benar
}
const userInput = "CAD-50"; // Asumsikan ini berasal dari input pengguna
// Ini akan dikompilasi, tetapi akan menyebabkan kesalahan runtime jika logika di dalam
// `processCurrency` bergantung pada formatnya.
processCurrency(userInput as FormattedCurrencyString);
Dalam kasus ini, kita melakukan casting userInput ke FormattedCurrencyString, melewati pemeriksaan waktu kompilasi TypeScript. Jika processCurrency bergantung pada string yang diformat dengan benar, itu akan mengalami kesalahan runtime.
Validasi runtime menjembatani kesenjangan ini dengan memverifikasi bahwa data yang diterima saat runtime sesuai dengan tipe TypeScript yang diharapkan.
Membangun Mesin Validasi Template Literal
Kita dapat membangun mesin validasi runtime menggunakan ekspresi reguler dan sistem tipe TypeScript. Mesin ini akan mengambil tipe literal template dan string sebagai input dan mengembalikan apakah string cocok dengan tipe tersebut.
Langkah 1: Mendefinisikan Tipe untuk Validasi Runtime
Pertama, kita memerlukan tipe generik yang dapat mewakili padanan runtime dari tipe literal template. Tipe ini harus mampu menangani berbagai jenis literal template, termasuk literal, gabungan (union), dan parameter tipe.
type TemplateLiteralToRegex =
T extends `${infer Start}${infer Middle}${infer End}`
? Start extends string
? Middle extends string
? End extends string
? TemplateLiteralToRegexStart & TemplateLiteralToRegexMiddle & TemplateLiteralToRegex
: never
: never
: never
: TemplateLiteralToRegexStart;
type TemplateLiteralToRegexStart = T extends `${infer Literal}` ? Literal : string;
type TemplateLiteralToRegexMiddle = T extends `${infer Literal}` ? Literal : string;
Definisi tipe rekursif ini memecah literal template menjadi bagian-bagian penyusunnya dan mengonversi setiap bagian menjadi pola ekspresi reguler.
Langkah 2: Mengimplementasikan Fungsi Validasi
Selanjutnya, kita mengimplementasikan fungsi validasi yang mengambil tipe literal template dan string yang akan divalidasi sebagai input. Fungsi ini menggunakan ekspresi reguler yang dihasilkan oleh TemplateLiteralToRegex untuk menguji string.
function isValid(str: string, templateType: T): boolean {
const regexPattern = `^${convertTemplateLiteralToRegex(templateType)}$`;
const regex = new RegExp(regexPattern);
return regex.test(str);
}
function convertTemplateLiteralToRegex(templateType: T): string {
// Konversi dasar untuk string literal - perluas ini untuk skenario yang lebih kompleks
return templateType.replace(/[.*+?^${'{'}()|\[\]]/g, '\\$&'); // Escape karakter regex khusus
}
Fungsi ini melakukan escape karakter ekspresi reguler khusus dan membuat ekspresi reguler dari tipe literal template, kemudian menguji string terhadap ekspresi reguler tersebut.
Langkah 3: Menggunakan Mesin Validasi
Sekarang, Anda dapat menggunakan fungsi isValid untuk memvalidasi string terhadap tipe literal template Anda pada saat runtime.
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const userInput1 = "USD-100";
const userInput2 = "CAD-50";
console.log(`'${userInput1}' valid: ${isValid(userInput1, "USD-100" )}`); // true
console.log(`'${userInput2}' valid: ${isValid(userInput2, "USD-100")}`); // false
console.log(`'${userInput1}' valid: ${isValid(userInput1, `USD-${100}`)}`); // true
console.log(`'${userInput2}' valid: ${isValid(userInput2, `USD-${100}`)}`); // false
Contoh ini menunjukkan cara menggunakan fungsi isValid untuk memvalidasi input pengguna terhadap tipe FormattedCurrencyString. Output akan menunjukkan apakah string input dianggap valid atau tidak, berdasarkan literal template yang ditentukan.
Skenario Validasi Lanjutan
Mesin validasi dasar dapat diperluas untuk menangani skenario yang lebih kompleks, seperti gabungan (union), tipe kondisional, dan tipe rekursif.
Menangani Gabungan (Union)
Untuk menangani gabungan (union), Anda dapat memodifikasi tipe TemplateLiteralToRegex untuk menghasilkan ekspresi reguler yang cocok dengan salah satu anggota gabungan (union).
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
function isValidCurrencyCode(str: string, templateType: T): boolean {
const currencyCodes: CurrencyCode[] = ["USD", "EUR", "GBP"];
return currencyCodes.includes(str as CurrencyCode);
}
function isValidUnionFormattedCurrencyString(str: string): boolean {
const parts = str.split('-');
if(parts.length !== 2) return false;
const [currencyCode, amount] = parts;
if (!isValidCurrencyCode(currencyCode, currencyCode)) return false;
if (isNaN(Number(amount))) return false;
return true;
}
console.log(`'USD-100' adalah string terformat yang valid: ${isValidUnionFormattedCurrencyString('USD-100')}`);
console.log(`'CAD-50' adalah string terformat yang valid: ${isValidUnionFormattedCurrencyString('CAD-50')}`);
Menangani Tipe Kondisional
Tipe kondisional dapat ditangani dengan mengevaluasi kondisi pada saat runtime dan menghasilkan ekspresi reguler yang berbeda berdasarkan hasilnya.
type IsString = T extends string ? true : false;
// Contoh ini memerlukan logika yang lebih canggih dan tidak sepenuhnya dapat diimplementasikan menggunakan regex sederhana.
// Penjaga tipe runtime menawarkan solusi yang lebih kuat dalam skenario spesifik ini.
// Kode di bawah ini bersifat ilustratif dan memerlukan adaptasi untuk menangani tipe kondisional yang kompleks.
function isString(value: any): value is string {
return typeof value === 'string';
}
function isValidConditionalType(value: any): boolean {
return isString(value);
}
console.log(`'hello' adalah string: ${isValidConditionalType('hello')}`);
console.log(`123 adalah string: ${isValidConditionalType(123)}`);
Menangani Tipe Rekursif
Tipe rekursif dapat ditangani dengan mendefinisikan fungsi rekursif yang menghasilkan pola ekspresi reguler. Namun, berhati-hatilah untuk menghindari rekursi tak terbatas dan kesalahan luapan tumpukan (stack overflow). Untuk rekursi yang dalam, pendekatan iteratif dengan batas yang sesuai sangat penting.
Alternatif untuk Ekspresi Reguler
Meskipun ekspresi reguler adalah alat yang ampuh untuk validasi string, mereka bisa menjadi kompleks dan sulit dipelihara. Pendekatan lain untuk validasi runtime meliputi:
- Fungsi Validasi Kustom: Tulis fungsi kustom untuk memvalidasi tipe tertentu berdasarkan persyaratan aplikasi Anda.
- Penjaga Tipe (Type Guards): Gunakan penjaga tipe (type guards) untuk mempersempit tipe variabel pada saat runtime.
- Pustaka Validasi: Manfaatkan pustaka validasi yang ada seperti Zod atau Yup untuk menyederhanakan proses validasi.
Zod, misalnya, menyediakan deklarasi berbasis skema yang diterjemahkan menjadi runtime validasi:
import { z } from 'zod';
const CurrencyCodeSchema = z.enum(['USD', 'EUR', 'GBP']);
const FormattedCurrencyStringSchema = z.string().regex(new RegExp(`^${CurrencyCodeSchema.enum.USD}|${CurrencyCodeSchema.enum.EUR}|${CurrencyCodeSchema.enum.GBP}-[0-9]+$`));
try {
const validCurrency = FormattedCurrencyStringSchema.parse("USD-100");
console.log("Mata Uang Valid:", validCurrency);
} catch (error) {
console.error("Mata Uang Tidak Valid:", error);
}
try {
const invalidCurrency = FormattedCurrencyStringSchema.parse("CAD-50");
console.log("Mata Uang Valid:", invalidCurrency); // Ini tidak akan dieksekusi jika parse gagal.
} catch (error) {
console.error("Mata Uang Tidak Valid:", error);
}
Praktik Terbaik untuk Validasi Runtime
Saat mengimplementasikan validasi runtime, perhatikan praktik terbaik berikut:
- Validasi di Batas: Validasi data segera setelah memasuki sistem Anda (misalnya, input pengguna, respons API).
- Berikan Pesan Kesalahan yang Jelas: Hasilkan pesan kesalahan yang informatif untuk membantu pengguna memahami mengapa input mereka tidak valid.
- Gunakan Strategi Validasi yang Konsisten: Terapkan strategi validasi yang konsisten di seluruh aplikasi Anda untuk memastikan integritas data.
- Uji Logika Validasi Anda: Uji logika validasi Anda secara menyeluruh untuk memastikan bahwa itu secara akurat mengidentifikasi data yang valid dan tidak valid.
- Seimbangkan Kinerja dan Keamanan: Optimalkan logika validasi Anda untuk kinerja sambil memastikan bahwa itu secara efektif mencegah kerentanan keamanan. Hindari regex yang terlalu rumit yang mengarah pada penolakan layanan (denial of service).
Pertimbangan Internasionalisasi
Saat berurusan dengan validasi string dalam konteks global, Anda perlu mempertimbangkan internasionalisasi (i18n) dan lokalisasi (l10n). Lokale yang berbeda mungkin memiliki aturan pemformatan string yang berbeda, seperti tanggal, angka, dan nilai mata uang.
Misalnya, simbol mata uang untuk Euro (€) dapat muncul sebelum atau sesudah jumlahnya, tergantung pada locale. Demikian pula, pemisah desimal bisa berupa titik (.) atau koma (,).
Untuk menangani variasi ini, Anda dapat menggunakan pustaka internasionalisasi seperti Intl, yang menyediakan API untuk memformat dan mengurai data yang sensitif terhadap locale. Misalnya, Anda dapat mengadaptasi contoh sebelumnya untuk menangani format mata uang yang berbeda:
function isValidCurrencyString(currencyString: string, locale: string): boolean {
try {
const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: currencyString.substring(0,3) }); //Contoh yang sangat dasar
// Cobalah untuk mengurai mata uang menggunakan formatter. Contoh ini sengaja sangat sederhana.
return true;
} catch (error) {
return false;
}
}
console.log(`USD-100 valid untuk en-US: ${isValidCurrencyString('USD-100', 'en-US')}`);
console.log(`EUR-100 valid untuk fr-FR: ${isValidCurrencyString('EUR-100', 'fr-FR')}`);
Cuplikan kode ini memberikan contoh dasar. Internasionalisasi yang tepat memerlukan penanganan yang lebih teliti, berpotensi memanfaatkan pustaka eksternal atau API yang secara khusus dirancang untuk pemformatan dan validasi mata uang di berbagai locale.
Kesimpulan
Validasi runtime adalah bagian penting dari membangun aplikasi TypeScript yang kuat dan andal. Dengan menggabungkan tipe literal template TypeScript dengan ekspresi reguler atau metode validasi alternatif, Anda dapat membuat mesin yang kuat untuk memverifikasi validitas string pada saat runtime.
Pendekatan ini meningkatkan keamanan tipe, mencegah kesalahan yang tidak terduga, dan meningkatkan kualitas kode Anda secara keseluruhan. Saat Anda membangun aplikasi yang lebih kompleks, pertimbangkan untuk memasukkan validasi runtime untuk memastikan bahwa data Anda sesuai dengan tipe dan format yang diharapkan.
Eksplorasi Lebih Lanjut
- Jelajahi teknik ekspresi reguler lanjutan untuk skenario validasi yang lebih kompleks.
- Selidiki pustaka validasi seperti Zod dan Yup untuk validasi berbasis skema.
- Pertimbangkan untuk menggunakan teknik pembuatan kode (code generation) untuk secara otomatis menghasilkan fungsi validasi dari tipe TypeScript.
- Pelajari pustaka dan API internasionalisasi untuk menangani data yang sensitif terhadap locale.