diff --git a/README.md b/README.md index 8978cd4..2256eb2 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ pnpm install tsukiko - ArrayParser - TupleParser - ObjectParser +- EnumParser - LiteralParser - IntersectionParser - UnionParser diff --git a/TODO.md b/TODO.md index 29d910f..4422222 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ # Todo list - [x] Function Parser -- [x] Instance Parser (instanceof) +- [x] Instance Parser (instance of) - [x] Class (Constructor) Parser (extends) -- [x] Promise Parser - [x] Tools: async parse +- [ ] export json schema diff --git a/package.json b/package.json index a8bd2e3..89638ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tsukiko", - "version": "1.2.6", + "version": "1.3.0", "description": "Dynamic Types Cheker At Runtime Which Develop", "main": "dist/index.js", "scripts": { diff --git a/src/factory.ts b/src/factory.ts index ada52dc..e46716b 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -1,76 +1,96 @@ import Parser from './parser'; import { - AnyParser, - ArrayParser, - BooleanParser, - NeverParser, - NullParser, - NumberParser, - ObjectParser, - StringParser, - TupleParser, - UndefinedParser, - UnknownParser, + AnyParser, + ArrayParser, + BooleanParser, + NeverParser, + NullParser, + NumberParser, + ObjectParser, + StringParser, + TupleParser, + UndefinedParser, + UnknownParser, + CustomParser, + IntersectionParser, + LiteralParser, + UnionParser, + EnumParser, + ClassParser, + FunctionParser } from './parsers'; -import { IonParserConfig, ObjectParserConfig, ParserFunction, TupleParserConfig } from './types'; -import { CustomParser, IntersectionParser, LiteralParser, UnionParser } from './parsers/advance'; +import { Constructor, IonParserConfig, ObjectParserConfig, ParserFunction, TupleParserConfig } from './types'; export function numberFactory() { - return new NumberParser(); + return new NumberParser(); } export function stringFactory() { - return new StringParser(); + return new StringParser(); } export function booleanFactory() { - return new BooleanParser(); + return new BooleanParser(); } export function undefinedFactory() { - return new UndefinedParser(); + return new UndefinedParser(); } export function nullFactory() { - return new NullParser(); + return new NullParser(); } export function anyFactory() { - return new AnyParser(); + return new AnyParser(); } export function unknownFactory() { - return new UnknownParser(); + return new UnknownParser(); } export function neverFactory() { - return new NeverParser(); + return new NeverParser(); } export function arrayFactory>(types: T) { - return new ArrayParser(types); + return new ArrayParser(types); } export function tupleFactory(types: T) { - return new TupleParser(types); + return new TupleParser(types); } -export function objectFactory(types: T) { - return new ObjectParser(types); +const emptyObject = {}; + +export function objectFactory(types?: T) { + return new ObjectParser(types ?? ({} as T)); } export function literalFactory(values: T) { - return new LiteralParser(values); + return new LiteralParser(values); +} + +export function intersectionFactory(...values: [...T]) { + return new IntersectionParser(...values); } -export function intersectionFactory(values: T) { - return new IntersectionParser(values); +export function unionFactory(...values: [...T]) { + return new UnionParser(...values); } -export function unionFactory(values: T) { - return new UnionParser(values); +export function enumFactory[]>(...values: T) { + return new EnumParser(...values); } export function customFactory(handle: ParserFunction) { - return new CustomParser(handle); + return new CustomParser(handle); +} + +export function functionFactory unknown = (...args: unknown[]) => unknown>() { + return new FunctionParser(); +} + +export function classFactory() { + return new ClassParser(); } diff --git a/src/index.ts b/src/index.ts index 7610b8d..62578e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,13 +3,16 @@ * @Blog: https://hotaru.icu * @Date: 2023-11-24 18:43:20 * @LastEditors: Hotaru biyuehuya@gmail.com - * @LastEditTime: 2023-12-10 15:38:50 + * @LastEditTime: 2024-06-10 18:41:03 */ import { anyFactory, arrayFactory, booleanFactory, + classFactory, customFactory, + enumFactory, + functionFactory, intersectionFactory, literalFactory, neverFactory, @@ -32,10 +35,11 @@ import type { ParserFunction } from './types'; import { DEFAULT_LANG } from './utils/lang'; +import { Constructor, IonParserConfig } from './types'; +import { Parser } from './parser'; export * from './factory'; export * from './types'; -export * from './parsers/advance'; export * from './parsers'; export * from './parser'; export * from './utils/error'; @@ -56,7 +60,10 @@ export namespace Tsu { export const Literal = literalFactory; export const Intersection = intersectionFactory; export const Union = unionFactory; + export const Enum = enumFactory; export const Custom = customFactory; + export const Function = functionFactory; + export const Class = classFactory; export type infer = ParserInfer; export type inferObject = ObjectParserInfer; export type inferTuple = TupleParserInfer; @@ -100,14 +107,23 @@ export function tsuFactory(lang: langType = DEFAULT_LANG): typeof Tsu { Literal(value) { return Tsu.Literal(value).lang(lang); }, - Intersection(values) { - return Tsu.Intersection(values).lang(lang); + Intersection(...values: T) { + return Tsu.Intersection(...values).lang(lang); }, - Union(value) { - return Tsu.Union(value).lang(lang); + Union(...value: T) { + return Tsu.Union(...value).lang(lang); + }, + Enum[]>(...values: T) { + return Tsu.Enum(...values).lang(lang); }, Custom(handle: ParserFunction) { return Tsu.Custom(handle).lang(lang); + }, + Function unknown = (...args: unknown[]) => unknown>() { + return functionFactory().lang(lang); + }, + Class() { + return classFactory(); } }; } diff --git a/src/lang/en_US.ts b/src/lang/en_US.ts index f1ccf48..d146201 100644 --- a/src/lang/en_US.ts +++ b/src/lang/en_US.ts @@ -1,58 +1,73 @@ export default { - /* string */ - not_string: 'Target is not a string', - not_a_email: 'Target string "%input%" is not an email', - not_a_domain: 'Target string "%input%" is not a domain', - not_a_url: 'Target string "%input%" is not a URL', - illegal_match_string: 'Target string "%input%" does not match the pattern %value%', - illegal_starts_with: 'Target string "%input%" does not start with %value%', - illegal_ends_with: 'Target string "%input%" does not end with %value%', - /* number */ - not_number: 'Target is not a number', - not_integer_number: 'Target number "%input%" is not an integer', - not_odd_number: 'Target number "%input%" is not an odd number', - not_even_number: 'Target number "%input%" is not an even number', - not_natural_number: 'Target number "%input%" is not a natural number ( >= 0 )', - not_positive_number: 'Target number "%input%" is not a positive number ( > 0 )', - not_negative_number: 'Target number "%input%" is not a negative number ( < 0 )', - not_percentage: 'Target number "%input%" is not a percentage ( >= 0, <= 1 )', - too_bigger: 'Target number "%input%" is too big, should be < %value%', - too_bigger_has: 'Target number "%input%" is too big, should be <= %value%', - too_smaller: 'Target number "%input%" is too small, should be > %value%', - too_smaller_has: 'Target number "%input%" is too small, should be >= %value%', - is_a_NaN: 'Target number is a NaN', - /* boolean */ - not_boolean: 'Target is not a boolean', - not_true: 'Target is not true', - not_false: 'Target is not false', - /* empty */ - not_null: 'Target is not null', - not_undefined: 'Target is not undefined', - /* extends */ - not_never: 'Target is not never', - /* stacks - array */ - not_an_array: 'Target is not an array', - array_error: 'Target array error at %length%: %value%', - /* stacks - tuple */ - not_a_tuple: 'Target is not a tuple', - illegal_tuple_length: 'Target tuple length should be %value%, not %input%', - tuple_error: 'Target tuple error at %length%: %value%', - /* stacks - object */ - not_an_object: 'Target is not an object', - object_is_null: 'Target object is null', - object_is_an_array: 'Target object is an array', - object_keys_too_many: 'Strict mode: too many keys for target object, should be %value% or less, not %input%', - object_error: 'Target object error at %key%: %value%', - object_key_error: 'Target object key type error', - /* advance - intersection */ - intersection_error_first: 'Intersection type first error: %value%', - intersection_error_second: 'Intersection type second error: %value%', - /* advance - union */ - union_error: 'Union type error: %value1%, %value2%', - /* advance - literal */ - literal_only: 'Literal types allow only strings and numbers', - literal_number_error: 'Target number cannot assign to %value%', - literal_string_error: 'Target string cannot assign to %value%', - /* advance - custom */ - custom_error: 'Cannot pass custom validation: %value%', + /* string */ + not_string: 'Target is not a string', + not_a_email: 'Target string "%input%" is not an email', + not_a_domain: 'Target string "%input%" is not a domain', + not_a_url: 'Target string "%input%" is not a URL', + illegal_match_string: 'Target string "%input%" does not match the pattern %value%', + illegal_starts_with: 'Target string "%input%" does not start with %value%', + illegal_ends_with: 'Target string "%input%" does not end with %value%', + /* number */ + not_number: 'Target is not a number', + not_integer_number: 'Target number "%input%" is not an integer', + not_odd_number: 'Target number "%input%" is not an odd number', + not_even_number: 'Target number "%input%" is not an even number', + not_natural_number: 'Target number "%input%" is not a natural number ( >= 0 )', + not_positive_number: 'Target number "%input%" is not a positive number ( > 0 )', + not_negative_number: 'Target number "%input%" is not a negative number ( < 0 )', + not_percentage: 'Target number "%input%" is not a percentage ( >= 0, <= 1 )', + too_bigger: 'Target number "%input%" is too big, should be < %value%', + too_bigger_has: 'Target number "%input%" is too big, should be <= %value%', + too_smaller: 'Target number "%input%" is too small, should be > %value%', + too_smaller_has: 'Target number "%input%" is too small, should be >= %value%', + is_a_NaN: 'Target number is a NaN', + /* boolean */ + not_boolean: 'Target is not a boolean', + not_true: 'Target is not true', + not_false: 'Target is not false', + /* empty */ + not_null: 'Target is not null', + not_undefined: 'Target is not undefined', + /* extends */ + not_never: 'Target is not never', + /* stacks - array */ + not_an_array: 'Target is not an array', + array_error: 'Target array error at %length%: %value%', + /* stacks - tuple */ + not_a_tuple: 'Target is not a tuple', + illegal_tuple_length: 'Target tuple length should be %value%, not %input%', + tuple_error: 'Target tuple error at %length%: %value%', + /* stacks - object */ + not_an_object: 'Target is not an object', + object_is_null: 'Target object is null', + object_is_an_array: 'Target object is an array', + object_not_instance_of_constructor: 'Target object is not an instance of constructor', + object_keys_too_many: 'Strict mode: too many keys for target object, should be %value% or less, not %input%', + object_error: 'Target object error at %key%: %value%', + object_key_error: 'Target object key type error', + /* advance - intersection */ + intersection_error_first: 'Intersection type first error: %value%', + intersection_error_second: 'Intersection type second error: %value%', + /* advance - union */ + union_error: 'Union type error: %value1%, %value2%', + /* advance - literal */ + literal_only: 'Literal types allow only strings and numbers', + literal_number_error: 'Target number cannot assign to %value%', + literal_string_error: 'Target string cannot assign to %value%', + /* advance - custom */ + custom_error: 'Cannot pass custom validation: %value%', + /* standard - function */ + not_a_function: 'Target is not a function', + not_a_constructor: 'Target is not a constructor', + not_an_async_function: 'Target is not an async function', + not_a_generator_function: 'Target is not a generator function', + not_an_async_generator_function: 'Target is not an async generator function', + not_an_arrow_function: 'Target is not an arrow function', + function_args_count_mismatch: 'Target function call arguments count mismatch, expected %expected%, got %actual%', + function_name_mismatch: 'Target function name mismatch: %value%', + /* standard - class */ + not_a_class: 'Target is not a class', + class_args_count_mismatch: 'Target class constructor arguments count mismatch, expected %expected%, got %actual%', + class_name_mismatch: 'Target class name mismatch: %value%', + class_prototype_error: 'Target class does not inherit specified Constructor' }; diff --git a/src/lang/ja_JP.ts b/src/lang/ja_JP.ts index fef593e..83a2b74 100644 --- a/src/lang/ja_JP.ts +++ b/src/lang/ja_JP.ts @@ -1,59 +1,76 @@ export default { - /* string */ - not_string: '対象は文字列ではありません', - not_a_email: '対象の文字列 "%input%" はメールアドレスではありません', - not_a_domain: '対象の文字列 "%input%" はドメインではありません', - not_a_url: '対象の文字列 "%input%" はURLではありません', - illegal_match_string: '対象の文字列 "%input%" は %value% のパターンと一致しません', - illegal_starts_with: '対象の文字列 "%input%" のprefixは %value% ではありません', - illegal_ends_with: '対象の文字列 "%input%" のsuffixは %value% ではありません', - /* number */ - not_number: '対象は数値ではありません', - not_integer_number: '対象の数値 "%input%" は整数ではありません', - not_odd_number: '対象の数値 "%input%" は奇数ではありません', - not_even_number: '対象の数値 "%input%"は偶数ではありません', - not_natural_number: '対象の数値 "%input%" は自然数ではありません ( >= 0 )', - not_positive_number: '対象の数値 "%input%" は正の数ではありません ( > 0 )', - not_negative_number: '対象の数値 "%input%" は負の数ではありません ( < 0 )', - not_percentage: '対象の数値 "%input%" はパーセンテージではありません ( >= 0, <= 1 )', - too_bigger: '対象の数値 "%input%" は大きすぎます。%value%未満であるべきです', - too_bigger_has: '対象の数値 "%input%" は大きすぎます。%value%以下であるべきです', - too_smaller: '対象の数値 "%input%" は小さすぎます。%value%より大きいべきです', - too_smaller_has: '対象の数値 "%input%" は小さすぎます。%value%以上であるべきです', - is_a_NaN: '対象の数値は NaN です', - /* boolean */ - not_boolean: '対象はブール値ではありません', - not_true: '対象は true ではありません', - not_false: '対象は false ではありません', - /* empty */ - not_null: '対象は null ではありません', - not_undefined: '対象は undefined ではありません', - /* extends */ - not_never: '対象は never ではありません', - /* stacks - array */ - not_an_array: '対象は配列ではありません', - array_error: '配列の %length% 番目でエラーが発生しました: %value%', - /* stacks - tuple */ - not_a_tuple: '対象はタプルではありません', - illegal_tuple_length: 'タプルの長さは %value% であるべきですが、 %input% でした', - tuple_error: 'タプルの %length% 番目でエラーが発生しました: %value%', - /* stacks - object */ - not_an_object: '対象はオブジェクトではありません', - object_is_null: '対象のオブジェクトは null です', - object_is_an_array: '対象のオブジェクトは配列です', - object_keys_too_many: - 'STRICTモード: オブジェクトのキーが多すぎます。%value%個以下であるべきですが、%input%個存在しました', - object_error: '%key% でエラーが発生しました: %value%', - object_key_error: '対象のオブジェクトのキーの型が間違っています', - /* advance - intersection */ - intersection_error_first: '交差型の最初の型でエラーが発生しました: %value%', - intersection_error_second: '交差型の2番目の型でエラーが発生しました: %value%', - /* advance - union */ - union_error: '合併型でエラーが発生しました: %value1%, %value2%', - /* advance - literal */ - literal_only: 'リテラル型は文字列と数値のみ許可されます', - literal_number_error: '対象の数値を %value% に代入できません', - literal_string_error: '対象の文字列を %value% に代入できません', - /* advance - custom */ - custom_error: 'カスタム検証をパスできませんでした: %value%', + /* string */ + not_string: '対象は文字列ではありません', + not_a_email: '対象の文字列 "%input%" はメールアドレスではありません', + not_a_domain: '対象の文字列 "%input%" はドメインではありません', + not_a_url: '対象の文字列 "%input%" はURLではありません', + illegal_match_string: '対象の文字列 "%input%" は %value% のパターンと一致しません', + illegal_starts_with: '対象の文字列 "%input%" のprefixは %value% ではありません', + illegal_ends_with: '対象の文字列 "%input%" のsuffixは %value% ではありません', + /* number */ + not_number: '対象は数値ではありません', + not_integer_number: '対象の数値 "%input%" は整数ではありません', + not_odd_number: '対象の数値 "%input%" は奇数ではありません', + not_even_number: '対象の数値 "%input%"は偶数ではありません', + not_natural_number: '対象の数値 "%input%" は自然数ではありません ( >= 0 )', + not_positive_number: '対象の数値 "%input%" は正の数ではありません ( > 0 )', + not_negative_number: '対象の数値 "%input%" は負の数ではありません ( < 0 )', + not_percentage: '対象の数値 "%input%" はパーセンテージではありません ( >= 0, <= 1 )', + too_bigger: '対象の数値 "%input%" は大きすぎます。%value%未満であるべきです', + too_bigger_has: '対象の数値 "%input%" は大きすぎます。%value%以下であるべきです', + too_smaller: '対象の数値 "%input%" は小さすぎます。%value%より大きいべきです', + too_smaller_has: '対象の数値 "%input%" は小さすぎます。%value%以上であるべきです', + is_a_NaN: '対象の数値は NaN です', + /* boolean */ + not_boolean: '対象はブール値ではありません', + not_true: '対象は true ではありません', + not_false: '対象は false ではありません', + /* empty */ + not_null: '対象は null ではありません', + not_undefined: '対象は undefined ではありません', + /* extends */ + not_never: '対象は never ではありません', + /* stacks - array */ + not_an_array: '対象は配列ではありません', + array_error: '配列の %length% 番目でエラーが発生しました: %value%', + /* stacks - tuple */ + not_a_tuple: '対象はタプルではありません', + illegal_tuple_length: 'タプルの長さは %value% であるべきですが、 %input% でした', + tuple_error: 'タプルの %length% 番目でエラーが発生しました: %value%', + /* stacks - object */ + not_an_object: '対象はオブジェクトではありません', + object_is_null: '対象のオブジェクトは null です', + object_is_an_array: '対象のオブジェクトは配列です', + object_not_instance_of_constructor: '対象のオブジェクトは インスタンスではありません', + object_keys_too_many: + 'STRICTモード: オブジェクトのキーが多すぎます。%value%個以下であるべきですが、%input%個存在しました', + object_error: '%key% でエラーが発生しました: %value%', + object_key_error: '対象のオブジェクトのキーの型が間違っています', + /* advance - intersection */ + intersection_error_first: '交差型の最初の型でエラーが発生しました: %value%', + intersection_error_second: '交差型の2番目の型でエラーが発生しました: %value%', + /* advance - union */ + union_error: '合併型でエラーが発生しました: %value1%, %value2%', + /* advance - literal */ + literal_only: 'リテラル型は文字列と数値のみ許可されます', + literal_number_error: '対象の数値を %value% に代入できません', + literal_string_error: '対象の文字列を %value% に代入できません', + /* advance - custom */ + custom_error: 'カスタム検証をパスできませんでした: %value%', + /* standard - function */ + not_a_function: '対象は関数ではありません', + not_a_constructor: '対象はコンストラクタではありません', + not_an_async_function: '対象は非同期関数ではありません', + not_a_generator_function: '対象はジェネレータ関数ではありません', + not_an_async_generator_function: '対象は非同期ジェネレータ関数ではありません', + not_an_arrow_function: '対象はアロー関数ではありません', + function_args_count_mismatch: + '対象の関数呼び出しの引数数が一致しません。期待値は %expected% ですが、実際の値は %actual% です', + function_name_mismatch: '対象の関数名が一致しません。期待値は %value% ですが、実際の値は %actual% です', + /* standard - class */ + not_a_class: '対象はクラスではありません', + class_args_count_mismatch: + '対象のクラスのコンストラクタの引数数が一致しません。期待値は %expected% ですが、実際の値は %actual% です', + class_name_mismatch: '対象のクラス名が一致しません。期待値は %value% ですが、実際の値は %actual% です', + class_prototype_error: '対象のクラスは指定されたコンストラクタを継承していません' }; diff --git a/src/lang/zh_CN.ts b/src/lang/zh_CN.ts index 5baedde..3ff507e 100644 --- a/src/lang/zh_CN.ts +++ b/src/lang/zh_CN.ts @@ -1,58 +1,73 @@ export default { - /* string */ - not_string: '目标不是 string 类型', - not_a_email: '目标 string “%input%” 不是一个 email', - not_a_domain: '目标 string “%input%” 目标不是一个 域名 ', - not_a_url: '目标 string “%input%” 不是一个 URL ', - illegal_match_string: '目标 string “%input%” 不符合规则 %value%', - illegal_starts_with: '目标 string “%input%” 开头字符不与 %value% 匹配', - illegal_ends_with: '目标 string “%input%” 末尾字符不与 %value% 匹配', - /* number */ - not_number: '目标不是 number 类型', - not_integer_number: '目标 number “%input%” 不是一个整数', - not_odd_number: '目标 number “%input%” 不是一个奇数', - not_even_number: '目标 number “%input%” 不是一个偶数', - not_natural_number: '目标 number “%input%” 不是一个自然数 ( >= 0 )', - not_positive_number: '目标 number “%input%” 不是一个正数 ( > 0 )', - not_negative_number: '目标 number “%input%” 不是一个负数 ( < 0 )', - not_percentage: '目标 number “%input%” 不是一个百分数 ( >= 0, <= 1 )', - too_bigger: '目标 number “%input%” 过大,应 < %value%', - too_bigger_has: '目标 number “%input%” 过大,应 <= %value%', - too_smaller: '目标 number “%input%” 过小,应 > %value%', - too_smaller_has: '目标 number “%input%” 过小,应 >= %value%', - is_a_NaN: '目标 number 是一个 NaN', - /* boolean */ - not_boolean: '目标不是 boolean 类型', - not_true: '目标不是 true 类型', - not_false: '目标不是 false 类型', - /* empty */ - not_null: '目标不是 null 类型', - not_undefined: '目标不是 undefined 类型', - /* extends */ - not_never: '目标不是 never 类型', - /* stacks - array */ - not_an_array: '目标不是一个 array 类型', - array_error: '目标 array 在 %length% 处发生错误:%value%', - /* stacks - tuple */ - not_a_tuple: '目标不是一个 tuple 类型', - illegal_tuple_length: '目标 tuple 的长度应是 %value%,而不是 %input%', - tuple_error: '目标 tuple 在 %length% 处发生错误:%value%', - /* stacks - object */ - not_an_object: '目标不是一个 object 类型', - object_is_null: '目标 object 是 null 类型', - object_is_an_array: '目标 object 是一个 array 类型', - object_keys_too_many: '严格模式:目标 object 键值数过多,应是 %value% 以内 而不是 %input%', - object_error: '目标 object 在 %key% 处发生错误:%value%', - object_key_error: '目标 object 键类型错误', - /* advance - intersection */ - intersection_error_first: '交叉类型的第一个类型发生错误:%value%', - intersection_error_second: '交叉类型的第二个类型发生错误:%value%', - /* advance - union */ - union_error: '联合类型发生错误:%value1% , %value2%', - /* advance - literal */ - literal_only: '字面量类型仅允许 string 与 number', - literal_number_error: '目标 number 不能赋给 %value%', - literal_string_error: '目标 string 不能赋给 %value%', - /* advance - custom */ - custom_error: '无法通过自定义规则: %value%', + /* string */ + not_string: '目标不是 string 类型', + not_a_email: '目标 string “%input%” 不是一个 email', + not_a_domain: '目标 string “%input%” 目标不是一个 域名 ', + not_a_url: '目标 string “%input%” 不是一个 URL ', + illegal_match_string: '目标 string “%input%” 不符合规则 %value%', + illegal_starts_with: '目标 string “%input%” 开头字符不与 %value% 匹配', + illegal_ends_with: '目标 string “%input%” 末尾字符不与 %value% 匹配', + /* number */ + not_number: '目标不是 number 类型', + not_integer_number: '目标 number “%input%” 不是一个整数', + not_odd_number: '目标 number “%input%” 不是一个奇数', + not_even_number: '目标 number “%input%” 不是一个偶数', + not_natural_number: '目标 number “%input%” 不是一个自然数 ( >= 0 )', + not_positive_number: '目标 number “%input%” 不是一个正数 ( > 0 )', + not_negative_number: '目标 number “%input%” 不是一个负数 ( < 0 )', + not_percentage: '目标 number “%input%” 不是一个百分数 ( >= 0, <= 1 )', + too_bigger: '目标 number “%input%” 过大,应 < %value%', + too_bigger_has: '目标 number “%input%” 过大,应 <= %value%', + too_smaller: '目标 number “%input%” 过小,应 > %value%', + too_smaller_has: '目标 number “%input%” 过小,应 >= %value%', + is_a_NaN: '目标 number 是一个 NaN', + /* boolean */ + not_boolean: '目标不是 boolean 类型', + not_true: '目标不是 true 类型', + not_false: '目标不是 false 类型', + /* empty */ + not_null: '目标不是 null 类型', + not_undefined: '目标不是 undefined 类型', + /* extends */ + not_never: '目标不是 never 类型', + /* stacks - array */ + not_an_array: '目标不是一个 array 类型', + array_error: '目标 array 在 %length% 处发生错误:%value%', + /* stacks - tuple */ + not_a_tuple: '目标不是一个 tuple 类型', + illegal_tuple_length: '目标 tuple 的长度应是 %value%,而不是 %input%', + tuple_error: '目标 tuple 在 %length% 处发生错误:%value%', + /* stacks - object */ + not_an_object: '目标不是一个 object 类型', + object_is_null: '目标 object 是 null 类型', + object_is_an_array: '目标 object 是一个 array 类型', + object_not_instance_of_constructor: '目标 object 不是构造函数的实例', + object_keys_too_many: '严格模式:目标 object 键值数过多,应是 %value% 以内 而不是 %input%', + object_error: '目标 object 在 %key% 处发生错误:%value%', + object_key_error: '目标 object 键类型错误', + /* advance - intersection */ + intersection_error_first: '交叉类型的第一个类型发生错误:%value%', + intersection_error_second: '交叉类型的第二个类型发生错误:%value%', + /* advance - union */ + union_error: '联合类型发生错误:%value1% , %value2%', + /* advance - literal */ + literal_only: '字面量类型仅允许 string 与 number', + literal_number_error: '目标 number 不能赋给 %value%', + literal_string_error: '目标 string 不能赋给 %value%', + /* advance - custom */ + custom_error: '无法通过自定义规则: %value%', + /* standard - function */ + not_a_function: '目标不是一个 function 类型', + not_a_constructor: '目标不是一个构造函数', + not_an_async_function: '目标不是一个 async function 类型', + not_a_generator_function: '目标不是一个 generator function 类型', + not_an_async_generator_function: '目标不是一个 async generator function 类型', + not_an_arrow_function: '目标不是一个 arrow function 类型', + function_args_count_mismatch: '目标 function 调用参数数目不匹配,应是 %expected%,而不是 %actual%', + function_name_mismatch: '目标 function 名称不匹配:%value%', + /* standard - class */ + not_a_class: '目标不是一个 class 类型', + class_args_count_mismatch: '目标 class 构造参数数目不匹配,应是 %expected%,而不是 %actual%', + class_name_mismatch: '目标 class 名称不匹配:%value%', + class_prototype_error: '目标 class 没有继承指定 Constructor' }; diff --git a/src/lang/zh_TW.ts b/src/lang/zh_TW.ts index 748d856..99eac3a 100644 --- a/src/lang/zh_TW.ts +++ b/src/lang/zh_TW.ts @@ -1,58 +1,73 @@ export default { - /* string */ - not_string: '目標不是 string 類型', - not_a_email: '目標 string 「%input%」 不是一個 email', - not_a_domain: '目標 string 「%input%」 目標不是一個 域名 ', - not_a_url: '目標 string 「%input%」 不是一個 URL ', - illegal_match_string: '目標 string 「%input%」 不符合規則 %value%', - illegal_starts_with: '目標 string 「%input%」 開頭字符不與 %value% 匹配', - illegal_ends_with: '目標 string 「%input%」 末尾字符不與 %value% 匹配', - /* number */ - not_number: '目標不是 number 類型', - not_integer_number: '目標 number 「%input%」 不是一個整數', - not_odd_number: '目標 number 「%input%」 不是一個奇數', - not_even_number: '目標 number 「%input%」 不是一個偶數', - not_natural_number: '目標 number 「%input%」 不是一個自然數 ( >= 0 )', - not_positive_number: '目標 number 「%input%」 不是一個正數 ( > 0 )', - not_negative_number: '目標 number 「%input%」 不是一個負數 ( < 0 )', - not_percentage: '目標 number 「%input%」 不是一個百分數 ( >= 0, <= 1 )', - too_bigger: '目標 number 「%input%」 過大,應 < %value%', - too_bigger_has: '目標 number 「%input%」 過大,應 <= %value%', - too_smaller: '目標 number 「%input%」 過小,應 > %value%', - too_smaller_has: '目標 number 「%input%」 過小,應 >= %value%', - is_a_NaN: '目標 number 是一個 NaN', - /* boolean */ - not_boolean: '目標不是 boolean 類型', - not_true: '目標不是 true 類型', - not_false: '目標不是 false 類型', - /* empty */ - not_null: '目標不是 null 類型', - not_undefined: '目標不是 undefined 類型', - /* extends */ - not_never: '目標不是 never 類型', - /* stacks - array */ - not_an_array: '目標不是一個 array 類型', - array_error: '目標 array 在 %length% 處發生錯誤:%value%', - /* stacks - tuple */ - not_a_tuple: '目標不是一個 tuple 類型', - illegal_tuple_length: '目標 tuple 的長度應是 %value%,而不是 %input%', - tuple_error: '目標 tuple 在 %length% 處發生錯誤:%value%', - /* stacks - object */ - not_an_object: '目標不是一個 object 類型', - object_is_null: '目標 object 是 null 類型', - object_is_an_array: '目標 object 是一個 array 類型', - object_keys_too_many: '嚴格模式:目標 object 鍵值數過多,應是 %value% 以內 而不是 %input%', - object_error: '目標 object 在 %key% 處發生錯誤:%value%', - object_key_error: '目標 object 鍵類型錯誤', - /* advance - intersection */ - intersection_error_first: '交叉類型的第一個類型發生錯誤:%value%', - intersection_error_second: '交叉類型的第二個類型發生錯誤:%value%', - /* advance - union */ - union_error: '聯合類型發生錯誤:%value1% , %value2%', - /* advance - literal */ - literal_only: '字面量類型僅允許 string 與 number', - literal_number_error: '目標 number 不能賦給 %value%', - literal_string_error: '目標 string 不能賦給 %value%', - /* advance - custom */ - custom_error: '無法通過自定義規則: %value%', + /* string */ + not_string: '目標不是 string 類型', + not_a_email: '目標 string 「%input%」 不是一個 email', + not_a_domain: '目標 string 「%input%」 目標不是一個 域名 ', + not_a_url: '目標 string 「%input%」 不是一個 URL ', + illegal_match_string: '目標 string 「%input%」 不符合規則 %value%', + illegal_starts_with: '目標 string 「%input%」 開頭字符不與 %value% 匹配', + illegal_ends_with: '目標 string 「%input%」 末尾字符不與 %value% 匹配', + /* number */ + not_number: '目標不是 number 類型', + not_integer_number: '目標 number 「%input%」 不是一個整數', + not_odd_number: '目標 number 「%input%」 不是一個奇數', + not_even_number: '目標 number 「%input%」 不是一個偶數', + not_natural_number: '目標 number 「%input%」 不是一個自然數 ( >= 0 )', + not_positive_number: '目標 number 「%input%」 不是一個正數 ( > 0 )', + not_negative_number: '目標 number 「%input%」 不是一個負數 ( < 0 )', + not_percentage: '目標 number 「%input%」 不是一個百分數 ( >= 0, <= 1 )', + too_bigger: '目標 number 「%input%」 過大,應 < %value%', + too_bigger_has: '目標 number 「%input%」 過大,應 <= %value%', + too_smaller: '目標 number 「%input%」 過小,應 > %value%', + too_smaller_has: '目標 number 「%input%」 過小,應 >= %value%', + is_a_NaN: '目標 number 是一個 NaN', + /* boolean */ + not_boolean: '目標不是 boolean 類型', + not_true: '目標不是 true 類型', + not_false: '目標不是 false 類型', + /* empty */ + not_null: '目標不是 null 類型', + not_undefined: '目標不是 undefined 類型', + /* extends */ + not_never: '目標不是 never 類型', + /* stacks - array */ + not_an_array: '目標不是一個 array 類型', + array_error: '目標 array 在 %length% 處發生錯誤:%value%', + /* stacks - tuple */ + not_a_tuple: '目標不是一個 tuple 類型', + illegal_tuple_length: '目標 tuple 的長度應是 %value%,而不是 %input%', + tuple_error: '目標 tuple 在 %length% 處發生錯誤:%value%', + /* stacks - object */ + not_an_object: '目標不是一個 object 類型', + object_is_null: '目標 object 是 null 類型', + object_is_an_array: '目標 object 是一個 array 類型', + object_not_instance_of_constructor: '目標 object 不是實例', + object_keys_too_many: '嚴格模式:目標 object 鍵值數過多,應是 %value% 以內 而不是 %input%', + object_error: '目標 object 在 %key% 處發生錯誤:%value%', + object_key_error: '目標 object 鍵類型錯誤', + /* advance - intersection */ + intersection_error_first: '交叉類型的第一個類型發生錯誤:%value%', + intersection_error_second: '交叉類型的第二個類型發生錯誤:%value%', + /* advance - union */ + union_error: '聯合類型發生錯誤:%value1% , %value2%', + /* advance - literal */ + literal_only: '字面量類型僅允許 string 與 number', + literal_number_error: '目標 number 不能賦給 %value%', + literal_string_error: '目標 string 不能賦給 %value%', + /* advance - custom */ + custom_error: '無法通過自定義規則: %value%', + /* standard - function */ + not_a_function: '目標不是一個 function 類型', + not_a_constructor: '目標不是一個 constructor 類型', + not_an_async_function: '目標不是一個 async function 類型', + not_a_generator_function: '目標不是一個 generator function 類型', + not_an_async_generator_function: '目標不是一個 async generator function 類型', + not_an_arrow_function: '目標不是一個 arrow function 類型', + function_args_count_mismatch: '目標 function 呼叫參數數目不符合規則,應為 %expected%,而不是 %actual%', + function_name_mismatch: '目標 function 名稱不符合規則:%value%', + /* standard - class */ + not_a_class: '目標不是一個 class 類型', + class_args_count_mismatch: '目標 class 建構參數數目不符合規則,應為 %expected%,而不是 %actual%', + class_name_mismatch: '目標 class 名稱不符合規則:%value%', + class_prototype_error: '目標 class 沒有繼承指定 Constructor' }; diff --git a/src/parser.ts b/src/parser.ts index c7117a7..82281cb 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -20,7 +20,7 @@ export abstract class Parser extends Lang implements ParserImpl { private defaultHandleBefore(input: T): T { const isEmpty = input === undefined || (!this.onlyEmpty && input === null); - if (isEmpty && !this.defaultValue && !this.isOptional) return undefined as T; + if (isEmpty && !this.defaultValue /* && !this.isOptional */) return undefined as T; return this.defaultHandle(isEmpty && this.defaultValue ? (this.defaultValue as T) : input); } @@ -38,6 +38,7 @@ export abstract class Parser extends Lang implements ParserImpl { private isOptional = false; + /* Allow undefined but not null */ private onlyEmpty = false; protected defaultValue?: T; @@ -57,6 +58,14 @@ export abstract class Parser extends Lang implements ParserImpl { } } + public parseAsync(input: unknown) { + return new Promise((resolve, reject) => { + const result = this.parseSafe(input); + if (result.value) resolve(result.data); + else reject(result.error); + }); + } + public check(input: unknown): input is T { return !this.testInput(input); } diff --git a/src/parsers/advance/enum.ts b/src/parsers/advance/enum.ts new file mode 100644 index 0000000..babdc96 --- /dev/null +++ b/src/parsers/advance/enum.ts @@ -0,0 +1,6 @@ +import { UnionParser } from './union'; +import Parser from '../../parser'; + +export class EnumParser[]> extends UnionParser {} + +export default UnionParser; diff --git a/src/parsers/advance/index.ts b/src/parsers/advance/index.ts deleted file mode 100644 index 653cb96..0000000 --- a/src/parsers/advance/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './literal'; -export * from './intersection'; -export * from './union'; -export * from './custom'; diff --git a/src/parsers/advance/intersection.ts b/src/parsers/advance/intersection.ts index ccd657a..807752b 100644 --- a/src/parsers/advance/intersection.ts +++ b/src/parsers/advance/intersection.ts @@ -2,30 +2,35 @@ import { ObjectParser } from '../..'; import Parser from '../../parser'; import type { IonParserConfig, ParserFunction, ParserInfer } from '../../types'; -export class IntersectionParser extends Parser & ParserInfer> { - private values: T; +/* eslint-disable @typescript-eslint/ban-types */ +type IntersectionFromArray = T extends [infer F, ...infer R] + ? ParserInfer & IntersectionFromArray + : {}; - protected rules: ParserFunction[] = []; +export class IntersectionParser extends Parser> { + private values: T; - public constructor(values: T) { - super(); - this.values = values; - this.rules.push(input => { - const result1 = this.values[0].parseSafe(input); - const result2 = this.values[1].parseSafe(input); - if (!result1.value) throw this.error('intersection_error_first', { value: result1.error.message }); - if (!result2.value) throw this.error('intersection_error_second', { value: result2.error.message }); - return null; - }); - } + protected rules: ParserFunction[] = []; - protected defaultHandle(input: ParserInfer & ParserInfer) { - if (!(this.values[0] instanceof ObjectParser && this.values[1] instanceof ObjectParser)) return input; - return Object.assign( - this.values[0].strict(false).parse(input), - this.values[1].strict(false).parse(input), - ) as ParserInfer & ParserInfer; - } + public constructor(...values: T) { + super(); + this.values = values; + this.rules.push((input) => { + const result1 = this.values[0].parseSafe(input); + const result2 = this.values[1].parseSafe(input); + if (!result1.value) throw this.error('intersection_error_first', { value: result1.error.message }); + if (!result2.value) throw this.error('intersection_error_second', { value: result2.error.message }); + return null; + }); + } + + protected defaultHandle(input: IntersectionFromArray) { + if (!(this.values[0] instanceof ObjectParser && this.values[1] instanceof ObjectParser)) return input; + return Object.assign( + this.values[0].strict(false).parse(input), + this.values[1].strict(false).parse(input) + ) as IntersectionFromArray; + } } export default IntersectionParser; diff --git a/src/parsers/advance/union.ts b/src/parsers/advance/union.ts index e3621af..46015db 100644 --- a/src/parsers/advance/union.ts +++ b/src/parsers/advance/union.ts @@ -2,28 +2,28 @@ import { ObjectParser } from '..'; import Parser from '../../parser'; import type { IonParserConfig, ParserFunction, ParserInfer } from '../../types'; -export class UnionParser extends Parser | ParserInfer> { - private values: T; +export class UnionParser extends Parser> { + private values: T; - protected rules: ParserFunction[] = []; + protected rules: ParserFunction[] = []; - public constructor(values: T) { - super(); - this.values = values; - this.rules.push(input => { - const result1 = this.values[0].parseSafe(input); - const result2 = this.values[1].parseSafe(input); - if (result1.value || result2.value) return null; - throw this.error('union_error', { value1: result1.error.message, value2: result2.error.message }); - }); - } + public constructor(...values: T) { + super(); + this.values = values; + this.rules.push((input) => { + const result1 = this.values[0].parseSafe(input); + const result2 = this.values[1].parseSafe(input); + if (result1.value || result2.value) return null; + throw this.error('union_error', { value1: result1.error.message, value2: result2.error.message }); + }); + } - protected defaultHandle(input: ParserInfer | ParserInfer) { - if (!(this.values[0] instanceof ObjectParser && this.values[1] instanceof ObjectParser)) return input; - const result = this.values[0].parseSafe(input); - if (result.value) return result.data as ParserInfer; - return this.values[1].parse(input) as ParserInfer; - } + protected defaultHandle(input: ParserInfer | ParserInfer) { + if (!(this.values[0] instanceof ObjectParser && this.values[1] instanceof ObjectParser)) return input; + const result = this.values[0].parseSafe(input); + if (result.value) return result.data as ParserInfer; + return this.values[1].parse(input) as ParserInfer; + } } export default UnionParser; diff --git a/src/parsers/any.ts b/src/parsers/any.ts index 0d736f7..e73a0c5 100644 --- a/src/parsers/any.ts +++ b/src/parsers/any.ts @@ -1,8 +1,9 @@ import type { ParserFunction } from '../types'; import Parser from '../parser'; +/* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export class AnyParser extends Parser { - protected rules: ParserFunction[] = [() => null]; + protected rules: ParserFunction[] = [() => null]; } export default AnyParser; diff --git a/src/parsers/index.ts b/src/parsers/index.ts index 85e61da..a5cba8e 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -1,11 +1,22 @@ -export * from './number' -export * from './string' -export * from './boolean' -export * from './null' -export * from './undefined' -export * from './any' -export * from './unknown' -export * from './never' -export * from './array' -export * from './tuple' -export * from './object' \ No newline at end of file +export * from './number'; +export * from './string'; +export * from './boolean'; +export * from './null'; +export * from './undefined'; +export * from './any'; +export * from './unknown'; +export * from './never'; +export * from './array'; +export * from './tuple'; +export * from './object'; + +/* Advance Types */ +export * from './advance/literal'; +export * from './advance/intersection'; +export * from './advance/union'; +export * from './advance/enum'; +export * from './advance/custom'; + +/* Standard Types */ +export * from './standard/function'; +export * from './standard/class'; diff --git a/src/parsers/object.ts b/src/parsers/object.ts index df32a56..90c3903 100644 --- a/src/parsers/object.ts +++ b/src/parsers/object.ts @@ -1,4 +1,4 @@ -import type { ObjectParserInfer, ObjectParserConfig, ParserFunction, ParserInfer } from '../types'; +import type { ObjectParserInfer, ObjectParserConfig, ParserFunction, ParserInfer, Constructor } from '../types'; import Parser from '../parser'; import { StringParser } from './string'; import TsuError from '../utils/error'; @@ -9,6 +9,8 @@ export class ObjectParser extends Parser input instanceof constructor).length !== this.constructors.length) + return this.error('object_not_instance_of_constructor'); const expectedLength = Object.keys(this.valuesParser).length; const realityLength = Object.keys(input).length; if (this.isStrict && realityLength > expectedLength) @@ -69,6 +71,8 @@ export class ObjectParser extends Parser extends Parser>>; } + + public instance(constructor: Constructor) { + this.constructors.push(constructor); + return this; + } } export default ObjectParser; diff --git a/src/parsers/standard/class.ts b/src/parsers/standard/class.ts new file mode 100644 index 0000000..d26876b --- /dev/null +++ b/src/parsers/standard/class.ts @@ -0,0 +1,42 @@ +import { StringParser } from '..'; +import Parser from '../../parser'; +import type { Constructor, ParserFunction } from '../../types'; + +export class ClassParser extends Parser { + private argsCount?: number; + + private nameParser?: StringParser; + + private constructors: Constructor[] = []; + + protected rules: ParserFunction[] = [ + (input) => { + if (typeof input !== 'function') return this.error('not_a_class'); + if (input.toString().startsWith('function')) return this.error('not_a_class'); + if (this.argsCount && input.length !== this.argsCount) + return this.error('class_args_count_mismatch', { expected: this.argsCount, actual: input.length }); + if (this.nameParser) { + const result = this.nameParser.parseSafe(input.name); + if (!result.value) return this.error('class_name_mismatch', { value: result.error.message }); + } + if ( + this.constructors.filter((Constructor) => Object.isPrototypeOf.call(Constructor, input.prototype)).length !== + this.constructors.length + ) + return this.error('class_prototype_error'); + return null; + } + ]; + + public args(count: number) { + this.argsCount = count; + return this; + } + + public name(parser: StringParser) { + this.nameParser = parser; + return this; + } +} + +export default ClassParser; diff --git a/src/parsers/standard/function.ts b/src/parsers/standard/function.ts new file mode 100644 index 0000000..049432e --- /dev/null +++ b/src/parsers/standard/function.ts @@ -0,0 +1,77 @@ +import { StringParser } from '..'; +import Parser from '../../parser'; +import type { ParserFunction } from '../../types'; + +const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor; +/* eslint-disable-next-line no-empty-function */ +const GeneratorFunction = Object.getPrototypeOf(function* Generator() {}).constructor; +/* eslint-disable-next-line no-empty-function */ +const AsyncGeneratorFunction = Object.getPrototypeOf(async function* AsyncGenerator() {}).constructor; + +export class FunctionParser unknown> extends Parser { + private isAsync: boolean = false; + + private isGenerator: boolean = false; + + private isArrow: boolean = false; + + private isConstructor: boolean = false; + + private argsCount?: number; + + private nameParser?: StringParser; + + protected rules: ParserFunction[] = [ + (input) => { + if (typeof input !== 'function') return this.error('not_a_function'); + if (input.toString().startsWith('class')) return this.error('not_a_function'); + if (this.isConstructor && input.name[0] !== input.name[0].toUpperCase()) return this.error('not_a_constructor'); + if (this.isAsync && !this.isGenerator && !(input instanceof AsyncFunction)) + return this.error('not_an_async_function'); + if (!this.isAsync && this.isGenerator && !(input instanceof GeneratorFunction)) + return this.error('not_a_generator_function'); + if (this.isAsync && this.isGenerator && !(input instanceof AsyncGeneratorFunction)) + return this.error('not_an_async_generator_function'); + if (this.isArrow && input.toString().includes('function')) return this.error('not_an_arrow_function'); + if (this.argsCount && input.length !== this.argsCount) + return this.error('function_args_count_mismatch', { expected: this.argsCount, actual: input.length }); + if (this.nameParser) { + const result = this.nameParser.parseSafe(input.name); + if (!result.value) return this.error('function_name_mismatch', { value: result.error.message }); + } + return null; + } + ]; + + public async(isAsync: boolean = true) { + this.isAsync = isAsync; + return this; + } + + public arrow(isArrow: boolean = true) { + this.isArrow = isArrow; + return this; + } + + public generator(isGenerator: boolean = true) { + this.isGenerator = isGenerator; + return this; + } + + public args(count: number) { + this.argsCount = count; + return this; + } + + public name(parser: StringParser) { + this.nameParser = parser; + return this; + } + + public constructed(isConstructor: boolean = true) { + this.isConstructor = isConstructor; + return this; + } +} + +export default FunctionParser; diff --git a/src/parsers/string.ts b/src/parsers/string.ts index 795eb2b..2c4b966 100644 --- a/src/parsers/string.ts +++ b/src/parsers/string.ts @@ -10,6 +10,7 @@ export class StringParser extends Parser { private isStrict: boolean = false; protected defaultHandle(input: string) { + super.defaultHandle(input); this.defaultHandle.toString(); return String(input); } diff --git a/src/types.ts b/src/types.ts index d2e4d07..584d552 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,9 @@ -import type enUS from './lang/ja_JP'; +import type enUS from './lang/en_US'; import type Parser from './parser'; import type TsuError from './utils/error'; +export type Constructor = new (...args: unknown[]) => unknown; + export type ParserInfer = T extends Parser ? R : never; export type GetParserClassType = T extends new () => Parser ? R : never; @@ -30,7 +32,7 @@ export type TupleParserInfer = { [K in keyof T]: ParserInfer; }; -export type IonParserConfig = [Parser, Parser]; +export type IonParserConfig = Parser[]; export type Langs = keyof typeof enUS; diff --git a/test/intersection.test.ts b/test/intersection.test.ts index 332a4c1..59769c3 100644 --- a/test/intersection.test.ts +++ b/test/intersection.test.ts @@ -1,13 +1,13 @@ import Tsu from '../src'; -test('insection parser', () => { - expect(Tsu.Intersection([Tsu.Number(), Tsu.Literal(2)]).check(2)).toBe(true); - expect(Tsu.Intersection([Tsu.Number(), Tsu.Literal(2)]).check(1)).toBe(false); - expect(Tsu.Intersection([Tsu.Object({ k: Tsu.Number() }), Tsu.Object({ v: Tsu.String() })]).check({ k: 1 })).toBe( +test('intersection parser', () => { + expect(Tsu.Intersection(Tsu.Number(), Tsu.Literal(2)).check(2)).toBe(true); + expect(Tsu.Intersection(Tsu.Number(), Tsu.Literal(2)).check(1)).toBe(false); + expect(Tsu.Intersection(Tsu.Object({ k: Tsu.Number() }), Tsu.Object({ v: Tsu.String() })).check({ k: 1 })).toBe( false ); expect( - Tsu.Union([Tsu.Object({ k: Tsu.Number() }).strict(), Tsu.Object({ v: Tsu.String().strict() })]).check({ + Tsu.Union(Tsu.Object({ k: Tsu.Number() }).strict(), Tsu.Object({ v: Tsu.String().strict() })).check({ k: 1, v: 2 }) diff --git a/test/literal.test.ts b/test/literal.test.ts index dc46d43..942a52b 100644 --- a/test/literal.test.ts +++ b/test/literal.test.ts @@ -1,17 +1,13 @@ import Tsu from '../src'; test('literal parser', () => { - expect(Tsu.Literal(1).check(1)).toBe(true); - expect(Tsu.Literal(1).check(2)).toBe(false); - expect(Tsu.Literal('1').check('1')).toBe(true); - expect(Tsu.Literal(1).check('1')).toBe(false); - expect(Tsu.Union([Tsu.Literal('1'), Tsu.Literal(1)]).check('1')).toBe(true); - expect(Tsu.Union([Tsu.Literal('1'), Tsu.Literal(1)]).check(1)).toBe(true); - expect(Tsu.Union([Tsu.Literal('1'), Tsu.Literal(1)]).check(2)).toBe(false); - expect( - Tsu.Union([Tsu.Literal('1'), Tsu.Literal(1)]) - .optional() - .check(undefined), - ).toBe(true); - expect(Tsu.Union([Tsu.Literal('1'), Tsu.Literal(1)]).check(undefined)).toBe(false); + expect(Tsu.Literal(1).check(1)).toBe(true); + expect(Tsu.Literal(1).check(2)).toBe(false); + expect(Tsu.Literal('1').check('1')).toBe(true); + expect(Tsu.Literal(1).check('1')).toBe(false); + expect(Tsu.Union(Tsu.Literal('1'), Tsu.Literal(1)).check('1')).toBe(true); + expect(Tsu.Union(Tsu.Literal('1'), Tsu.Literal(1)).check(1)).toBe(true); + expect(Tsu.Union(Tsu.Literal('1'), Tsu.Literal(1)).check(2)).toBe(false); + expect(Tsu.Union(Tsu.Literal('1'), Tsu.Literal(1)).optional().check(undefined)).toBe(true); + expect(Tsu.Union(Tsu.Literal('1'), Tsu.Literal(1)).check(undefined)).toBe(false); }); diff --git a/test/string.test.ts b/test/string.test.ts index 9cc4b36..77ce3a3 100644 --- a/test/string.test.ts +++ b/test/string.test.ts @@ -28,4 +28,5 @@ test('string parser', () => { expect(Tsu.String().check(123)).toBe(true); expect(Tsu.String().strict().check(123)).toBe(false); expect(Tsu.String().parse(123)).toBe('123'); + expect(Tsu.String().optional().parse(null)).toBe(undefined); }); diff --git a/test/union.test.ts b/test/union.test.ts index a029736..ed9ae89 100644 --- a/test/union.test.ts +++ b/test/union.test.ts @@ -1,21 +1,19 @@ import Tsu from '../src'; test('union parser', () => { - expect(Tsu.Union([Tsu.Number(), Tsu.String()]).check(1)).toBe(true); - expect(Tsu.Union([Tsu.Number(), Tsu.Literal(2)]).check(2)).toBe(true); - expect(Tsu.Union([Tsu.Number(), Tsu.Literal(2)]).check(1)).toBe(true); - expect(Tsu.Union([Tsu.Number(), Tsu.Literal(2)]).check('1')).toBe(false); - expect(Tsu.Union([Tsu.Object({ k: Tsu.Number() }), Tsu.Object({ v: Tsu.String() })]).check({ k: 1, v: 2 })).toBe( - true, - ); - expect( - Tsu.Union([Tsu.Object({ k: Tsu.Number() }), Tsu.Object({ v: Tsu.String() })]).check({ k: 1, v: 2, s: 3 }), - ).toBe(true); - expect( - Tsu.Union([Tsu.Object({ k: Tsu.Number() }).strict(), Tsu.Object({ v: Tsu.String() }).strict()]).check({ - k: 1, - v: 2, - }), - ).toBe(false); - expect(Tsu.Union([Tsu.Object({ k: Tsu.Number() }), Tsu.Object({ v: Tsu.String() })]).check({ k: 1 })).toBe(true); + expect(Tsu.Union(Tsu.Number(), Tsu.String()).check(1)).toBe(true); + expect(Tsu.Union(Tsu.Number(), Tsu.Literal(2)).check(2)).toBe(true); + expect(Tsu.Union(Tsu.Number(), Tsu.Literal(2)).check(1)).toBe(true); + expect(Tsu.Union(Tsu.Number(), Tsu.Literal(2)).check('1')).toBe(false); + expect(Tsu.Union(Tsu.Object({ k: Tsu.Number() }), Tsu.Object({ v: Tsu.String() })).check({ k: 1, v: 2 })).toBe(true); + expect(Tsu.Union(Tsu.Object({ k: Tsu.Number() }), Tsu.Object({ v: Tsu.String() })).check({ k: 1, v: 2, s: 3 })).toBe( + true + ); + expect( + Tsu.Union(Tsu.Object({ k: Tsu.Number() }).strict(), Tsu.Object({ v: Tsu.String() }).strict()).check({ + k: 1, + v: 2 + }) + ).toBe(false); + expect(Tsu.Union(Tsu.Object({ k: Tsu.Number() }), Tsu.Object({ v: Tsu.String() })).check({ k: 1 })).toBe(true); });