@lowerdeck/validation 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/.turbo/turbo-build.log +11 -0
  2. package/.turbo/turbo-test.log +144 -0
  3. package/README.md +158 -0
  4. package/dist/index.cjs +2 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.ts +119 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.modern.js +2 -0
  9. package/dist/index.modern.js.map +1 -0
  10. package/dist/index.module.js +2 -0
  11. package/dist/index.module.js.map +1 -0
  12. package/dist/index.umd.js +2 -0
  13. package/dist/index.umd.js.map +1 -0
  14. package/dist/lib/introspect.d.ts +13 -0
  15. package/dist/lib/introspect.d.ts.map +1 -0
  16. package/dist/lib/result.d.ts +4 -0
  17. package/dist/lib/result.d.ts.map +1 -0
  18. package/dist/lib/result.test.d.ts +2 -0
  19. package/dist/lib/result.test.d.ts.map +1 -0
  20. package/dist/lib/types.d.ts +48 -0
  21. package/dist/lib/types.d.ts.map +1 -0
  22. package/dist/lib/validator.d.ts +3 -0
  23. package/dist/lib/validator.d.ts.map +1 -0
  24. package/dist/lib/validator.test.d.ts +2 -0
  25. package/dist/lib/validator.test.d.ts.map +1 -0
  26. package/dist/modifiers/color.d.ts +5 -0
  27. package/dist/modifiers/color.d.ts.map +1 -0
  28. package/dist/modifiers/color.test.d.ts +2 -0
  29. package/dist/modifiers/color.test.d.ts.map +1 -0
  30. package/dist/modifiers/email.d.ts +5 -0
  31. package/dist/modifiers/email.d.ts.map +1 -0
  32. package/dist/modifiers/email.test.d.ts +2 -0
  33. package/dist/modifiers/email.test.d.ts.map +1 -0
  34. package/dist/modifiers/emoji.d.ts +5 -0
  35. package/dist/modifiers/emoji.d.ts.map +1 -0
  36. package/dist/modifiers/emoji.test.d.ts +2 -0
  37. package/dist/modifiers/emoji.test.d.ts.map +1 -0
  38. package/dist/modifiers/endsWith.d.ts +5 -0
  39. package/dist/modifiers/endsWith.d.ts.map +1 -0
  40. package/dist/modifiers/endsWith.test.d.ts +2 -0
  41. package/dist/modifiers/endsWith.test.d.ts.map +1 -0
  42. package/dist/modifiers/equals.d.ts +8 -0
  43. package/dist/modifiers/equals.d.ts.map +1 -0
  44. package/dist/modifiers/equals.test.d.ts +2 -0
  45. package/dist/modifiers/equals.test.d.ts.map +1 -0
  46. package/dist/modifiers/includes.d.ts +8 -0
  47. package/dist/modifiers/includes.d.ts.map +1 -0
  48. package/dist/modifiers/includes.test.d.ts +2 -0
  49. package/dist/modifiers/includes.test.d.ts.map +1 -0
  50. package/dist/modifiers/index.d.ts +18 -0
  51. package/dist/modifiers/index.d.ts.map +1 -0
  52. package/dist/modifiers/integer.d.ts +5 -0
  53. package/dist/modifiers/integer.d.ts.map +1 -0
  54. package/dist/modifiers/integer.test.d.ts +2 -0
  55. package/dist/modifiers/integer.test.d.ts.map +1 -0
  56. package/dist/modifiers/ip.d.ts +11 -0
  57. package/dist/modifiers/ip.d.ts.map +1 -0
  58. package/dist/modifiers/ip.test.d.ts +2 -0
  59. package/dist/modifiers/ip.test.d.ts.map +1 -0
  60. package/dist/modifiers/isoDate.d.ts +11 -0
  61. package/dist/modifiers/isoDate.d.ts.map +1 -0
  62. package/dist/modifiers/isoDate.test.d.ts +2 -0
  63. package/dist/modifiers/isoDate.test.d.ts.map +1 -0
  64. package/dist/modifiers/length.d.ts +9 -0
  65. package/dist/modifiers/length.d.ts.map +1 -0
  66. package/dist/modifiers/length.test.d.ts +2 -0
  67. package/dist/modifiers/length.test.d.ts.map +1 -0
  68. package/dist/modifiers/multipleOf.d.ts +5 -0
  69. package/dist/modifiers/multipleOf.d.ts.map +1 -0
  70. package/dist/modifiers/multipleOf.test.d.ts +2 -0
  71. package/dist/modifiers/multipleOf.test.d.ts.map +1 -0
  72. package/dist/modifiers/oneOf.d.ts +5 -0
  73. package/dist/modifiers/oneOf.d.ts.map +1 -0
  74. package/dist/modifiers/oneOf.test.d.ts +2 -0
  75. package/dist/modifiers/oneOf.test.d.ts.map +1 -0
  76. package/dist/modifiers/positive.d.ts +8 -0
  77. package/dist/modifiers/positive.d.ts.map +1 -0
  78. package/dist/modifiers/positive.test.d.ts +2 -0
  79. package/dist/modifiers/positive.test.d.ts.map +1 -0
  80. package/dist/modifiers/regex.d.ts +5 -0
  81. package/dist/modifiers/regex.d.ts.map +1 -0
  82. package/dist/modifiers/regex.test.d.ts +2 -0
  83. package/dist/modifiers/regex.test.d.ts.map +1 -0
  84. package/dist/modifiers/size.d.ts +8 -0
  85. package/dist/modifiers/size.d.ts.map +1 -0
  86. package/dist/modifiers/size.test.d.ts +2 -0
  87. package/dist/modifiers/size.test.d.ts.map +1 -0
  88. package/dist/modifiers/startsWith.d.ts +5 -0
  89. package/dist/modifiers/startsWith.d.ts.map +1 -0
  90. package/dist/modifiers/startsWith.test.d.ts +2 -0
  91. package/dist/modifiers/startsWith.test.d.ts.map +1 -0
  92. package/dist/modifiers/url.d.ts +6 -0
  93. package/dist/modifiers/url.d.ts.map +1 -0
  94. package/dist/modifiers/url.test.d.ts +2 -0
  95. package/dist/modifiers/url.test.d.ts.map +1 -0
  96. package/dist/transformers/case.d.ts +4 -0
  97. package/dist/transformers/case.d.ts.map +1 -0
  98. package/dist/transformers/case.test.d.ts +2 -0
  99. package/dist/transformers/case.test.d.ts.map +1 -0
  100. package/dist/transformers/index.d.ts +3 -0
  101. package/dist/transformers/index.d.ts.map +1 -0
  102. package/dist/transformers/trim.d.ts +5 -0
  103. package/dist/transformers/trim.d.ts.map +1 -0
  104. package/dist/transformers/trim.test.d.ts +2 -0
  105. package/dist/transformers/trim.test.d.ts.map +1 -0
  106. package/dist/validators/any.d.ts +4 -0
  107. package/dist/validators/any.d.ts.map +1 -0
  108. package/dist/validators/any.test.d.ts +2 -0
  109. package/dist/validators/any.test.d.ts.map +1 -0
  110. package/dist/validators/array.d.ts +3 -0
  111. package/dist/validators/array.d.ts.map +1 -0
  112. package/dist/validators/array.test.d.ts +2 -0
  113. package/dist/validators/array.test.d.ts.map +1 -0
  114. package/dist/validators/boolean.d.ts +3 -0
  115. package/dist/validators/boolean.d.ts.map +1 -0
  116. package/dist/validators/boolean.test.d.ts +2 -0
  117. package/dist/validators/boolean.test.d.ts.map +1 -0
  118. package/dist/validators/date.d.ts +3 -0
  119. package/dist/validators/date.d.ts.map +1 -0
  120. package/dist/validators/date.test.d.ts +2 -0
  121. package/dist/validators/date.test.d.ts.map +1 -0
  122. package/dist/validators/enum.d.ts +6 -0
  123. package/dist/validators/enum.d.ts.map +1 -0
  124. package/dist/validators/enum.test.d.ts +2 -0
  125. package/dist/validators/enum.test.d.ts.map +1 -0
  126. package/dist/validators/index.d.ts +17 -0
  127. package/dist/validators/index.d.ts.map +1 -0
  128. package/dist/validators/intersection.d.ts +3 -0
  129. package/dist/validators/intersection.d.ts.map +1 -0
  130. package/dist/validators/intersection.test.d.ts +2 -0
  131. package/dist/validators/intersection.test.d.ts.map +1 -0
  132. package/dist/validators/literal.d.ts +3 -0
  133. package/dist/validators/literal.d.ts.map +1 -0
  134. package/dist/validators/literal.test.d.ts +2 -0
  135. package/dist/validators/literal.test.d.ts.map +1 -0
  136. package/dist/validators/null.d.ts +3 -0
  137. package/dist/validators/null.d.ts.map +1 -0
  138. package/dist/validators/null.test.d.ts +2 -0
  139. package/dist/validators/null.test.d.ts.map +1 -0
  140. package/dist/validators/nullable.d.ts +3 -0
  141. package/dist/validators/nullable.d.ts.map +1 -0
  142. package/dist/validators/nullable.test.d.ts +2 -0
  143. package/dist/validators/nullable.test.d.ts.map +1 -0
  144. package/dist/validators/number.d.ts +3 -0
  145. package/dist/validators/number.d.ts.map +1 -0
  146. package/dist/validators/number.test.d.ts +2 -0
  147. package/dist/validators/number.test.d.ts.map +1 -0
  148. package/dist/validators/object.d.ts +12 -0
  149. package/dist/validators/object.d.ts.map +1 -0
  150. package/dist/validators/object.test.d.ts +2 -0
  151. package/dist/validators/object.test.d.ts.map +1 -0
  152. package/dist/validators/optional.d.ts +3 -0
  153. package/dist/validators/optional.d.ts.map +1 -0
  154. package/dist/validators/optional.test.d.ts +2 -0
  155. package/dist/validators/optional.test.d.ts.map +1 -0
  156. package/dist/validators/record.d.ts +3 -0
  157. package/dist/validators/record.d.ts.map +1 -0
  158. package/dist/validators/record.test.d.ts +2 -0
  159. package/dist/validators/record.test.d.ts.map +1 -0
  160. package/dist/validators/string.d.ts +3 -0
  161. package/dist/validators/string.d.ts.map +1 -0
  162. package/dist/validators/string.test.d.ts +2 -0
  163. package/dist/validators/string.test.d.ts.map +1 -0
  164. package/dist/validators/union.d.ts +3 -0
  165. package/dist/validators/union.d.ts.map +1 -0
  166. package/dist/validators/union.test.d.ts +2 -0
  167. package/dist/validators/union.test.d.ts.map +1 -0
  168. package/dist/validators/void.d.ts +3 -0
  169. package/dist/validators/void.d.ts.map +1 -0
  170. package/package.json +31 -0
  171. package/src/index.ts +33 -0
  172. package/src/lib/introspect.ts +33 -0
  173. package/src/lib/result.test.ts +80 -0
  174. package/src/lib/result.ts +11 -0
  175. package/src/lib/types.ts +57 -0
  176. package/src/lib/validator.test.ts +82 -0
  177. package/src/lib/validator.ts +63 -0
  178. package/src/modifiers/color.test.ts +29 -0
  179. package/src/modifiers/color.ts +18 -0
  180. package/src/modifiers/email.test.ts +29 -0
  181. package/src/modifiers/email.ts +18 -0
  182. package/src/modifiers/emoji.test.ts +31 -0
  183. package/src/modifiers/emoji.ts +18 -0
  184. package/src/modifiers/endsWith.test.ts +50 -0
  185. package/src/modifiers/endsWith.ts +18 -0
  186. package/src/modifiers/equals.test.ts +70 -0
  187. package/src/modifiers/equals.ts +41 -0
  188. package/src/modifiers/includes.test.ts +64 -0
  189. package/src/modifiers/includes.ts +35 -0
  190. package/src/modifiers/index.ts +17 -0
  191. package/src/modifiers/integer.test.ts +45 -0
  192. package/src/modifiers/integer.ts +18 -0
  193. package/src/modifiers/ip.test.ts +34 -0
  194. package/src/modifiers/ip.ts +50 -0
  195. package/src/modifiers/isoDate.test.ts +83 -0
  196. package/src/modifiers/isoDate.ts +51 -0
  197. package/src/modifiers/length.test.ts +107 -0
  198. package/src/modifiers/length.ts +54 -0
  199. package/src/modifiers/multipleOf.test.ts +51 -0
  200. package/src/modifiers/multipleOf.ts +18 -0
  201. package/src/modifiers/oneOf.test.ts +27 -0
  202. package/src/modifiers/oneOf.ts +16 -0
  203. package/src/modifiers/positive.test.ts +64 -0
  204. package/src/modifiers/positive.ts +35 -0
  205. package/src/modifiers/regex.test.ts +40 -0
  206. package/src/modifiers/regex.ts +17 -0
  207. package/src/modifiers/size.test.ts +84 -0
  208. package/src/modifiers/size.ts +37 -0
  209. package/src/modifiers/startsWith.test.ts +40 -0
  210. package/src/modifiers/startsWith.ts +18 -0
  211. package/src/modifiers/url.test.ts +48 -0
  212. package/src/modifiers/url.ts +30 -0
  213. package/src/transformers/case.test.ts +36 -0
  214. package/src/transformers/case.ts +5 -0
  215. package/src/transformers/index.ts +2 -0
  216. package/src/transformers/trim.test.ts +32 -0
  217. package/src/transformers/trim.ts +7 -0
  218. package/src/validators/any.test.ts +11 -0
  219. package/src/validators/any.ts +11 -0
  220. package/src/validators/array.test.ts +46 -0
  221. package/src/validators/array.ts +43 -0
  222. package/src/validators/boolean.test.ts +39 -0
  223. package/src/validators/boolean.ts +34 -0
  224. package/src/validators/date.test.ts +39 -0
  225. package/src/validators/date.ts +26 -0
  226. package/src/validators/enum.test.ts +25 -0
  227. package/src/validators/enum.ts +56 -0
  228. package/src/validators/index.ts +16 -0
  229. package/src/validators/intersection.test.ts +35 -0
  230. package/src/validators/intersection.ts +24 -0
  231. package/src/validators/literal.test.ts +42 -0
  232. package/src/validators/literal.ts +26 -0
  233. package/src/validators/null.test.ts +39 -0
  234. package/src/validators/null.ts +18 -0
  235. package/src/validators/nullable.test.ts +34 -0
  236. package/src/validators/nullable.ts +16 -0
  237. package/src/validators/number.test.ts +39 -0
  238. package/src/validators/number.ts +23 -0
  239. package/src/validators/object.test.ts +66 -0
  240. package/src/validators/object.ts +66 -0
  241. package/src/validators/optional.test.ts +33 -0
  242. package/src/validators/optional.ts +16 -0
  243. package/src/validators/record.test.ts +56 -0
  244. package/src/validators/record.ts +56 -0
  245. package/src/validators/string.test.ts +39 -0
  246. package/src/validators/string.ts +30 -0
  247. package/src/validators/union.test.ts +73 -0
  248. package/src/validators/union.ts +38 -0
  249. package/src/validators/void.ts +6 -0
  250. package/tsconfig.json +9 -0
@@ -0,0 +1,36 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { lowerCase, upperCase } from './case';
3
+
4
+ describe('case transformers', () => {
5
+ describe('upperCase', () => {
6
+ test('should transform a string to uppercase', () => {
7
+ let input = 'hello world';
8
+ let expectedOutput = 'HELLO WORLD';
9
+ let output = upperCase(input);
10
+ expect(output).toEqual(expectedOutput);
11
+ });
12
+
13
+ test('should return an empty string if input is empty', () => {
14
+ let input = '';
15
+ let expectedOutput = '';
16
+ let output = upperCase(input);
17
+ expect(output).toEqual(expectedOutput);
18
+ });
19
+ });
20
+
21
+ describe('lowerCase', () => {
22
+ test('should transform a string to lowercase', () => {
23
+ let input = 'HELLO WORLD';
24
+ let expectedOutput = 'hello world';
25
+ let output = lowerCase(input);
26
+ expect(output).toEqual(expectedOutput);
27
+ });
28
+
29
+ test('should return an empty string if input is empty', () => {
30
+ let input = '';
31
+ let expectedOutput = '';
32
+ let output = lowerCase(input);
33
+ expect(output).toEqual(expectedOutput);
34
+ });
35
+ });
36
+ });
@@ -0,0 +1,5 @@
1
+ import { Transformer } from '../lib/types';
2
+
3
+ export let upperCase: Transformer<string> = value => value.toUpperCase();
4
+
5
+ export let lowerCase: Transformer<string> = value => value.toLowerCase();
@@ -0,0 +1,2 @@
1
+ export * from './case';
2
+ export * from './trim';
@@ -0,0 +1,32 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { trim, trimEnd, trimStart } from './trim';
3
+
4
+ describe('trim', () => {
5
+ test('should trim whitespace from both ends of the string', () => {
6
+ expect(trim(' hello ')).toBe('hello');
7
+ });
8
+
9
+ test('should return an empty string if given an empty string', () => {
10
+ expect(trim('')).toBe('');
11
+ });
12
+ });
13
+
14
+ describe('trimStart', () => {
15
+ test('should trim whitespace from the beginning of the string', () => {
16
+ expect(trimStart(' hello')).toBe('hello');
17
+ });
18
+
19
+ test('should return an empty string if given an empty string', () => {
20
+ expect(trimStart('')).toBe('');
21
+ });
22
+ });
23
+
24
+ describe('trimEnd', () => {
25
+ test('should trim whitespace from the end of the string', () => {
26
+ expect(trimEnd('hello ')).toBe('hello');
27
+ });
28
+
29
+ test('should return an empty string if given an empty string', () => {
30
+ expect(trimEnd('')).toBe('');
31
+ });
32
+ });
@@ -0,0 +1,7 @@
1
+ import { Transformer } from '../lib/types';
2
+
3
+ export let trim: Transformer<string> = value => value.trim();
4
+
5
+ export let trimStart: Transformer<string> = value => value.trimStart();
6
+
7
+ export let trimEnd: Transformer<string> = value => value.trimEnd();
@@ -0,0 +1,11 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { any } from './any';
3
+
4
+ describe('any', () => {
5
+ test('should always return success with the input value', () => {
6
+ let input = 'hello world';
7
+ let result: any = any({}).validate(input);
8
+ expect(result.success).toBe(true);
9
+ expect(result.value).toBe(input);
10
+ });
11
+ });
@@ -0,0 +1,11 @@
1
+ import { ValidatorOptions } from '../lib/types';
2
+ import { createValidator } from '../lib/validator';
3
+
4
+ export let any = createValidator<any, ValidatorOptions<any>>('any', (opts, value) => {
5
+ return { success: true, value };
6
+ });
7
+
8
+ export let typedAny = <T>(name: string) =>
9
+ createValidator<T, ValidatorOptions<T>>(name, (opts, value) => {
10
+ return { success: true, value };
11
+ })();
@@ -0,0 +1,46 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { error, success } from '../lib/result';
3
+ import { array } from './array';
4
+ import { string } from './string';
5
+
6
+ describe('array', () => {
7
+ test('should validate an array of strings', () => {
8
+ let validator = array(string());
9
+ let result = validator.validate(['hello', 'world']);
10
+
11
+ expect(result).toEqual(success(['hello', 'world']));
12
+ });
13
+
14
+ test('should return an error for invalid input', () => {
15
+ let validator = array(string());
16
+ let result = validator.validate('not an array');
17
+
18
+ expect(result).toEqual(
19
+ error([
20
+ {
21
+ code: 'invalid_type',
22
+ message: 'Invalid input, expected array, received string',
23
+ received: 'string',
24
+ expected: 'array'
25
+ }
26
+ ])
27
+ );
28
+ });
29
+
30
+ test('should return an error for invalid array items', () => {
31
+ let validator = array(string());
32
+ let result = validator.validate(['hello', 123]);
33
+
34
+ expect(result).toEqual(
35
+ error([
36
+ {
37
+ code: 'invalid_type',
38
+ message: 'Invalid input, expected string, received number',
39
+ received: 'number',
40
+ expected: 'string',
41
+ path: ['1']
42
+ }
43
+ ])
44
+ );
45
+ });
46
+ });
@@ -0,0 +1,43 @@
1
+ import { error, success } from '../lib/result';
2
+ import { ValidationType, ValidationTypeValue, ValidatorOptions } from '../lib/types';
3
+
4
+ export let array = <Validator extends ValidationType<any>>(
5
+ validator: Validator,
6
+ opts?: ValidatorOptions<ValidationTypeValue<Validator>[]>
7
+ ): ValidationType<ValidationTypeValue<Validator>[]> => ({
8
+ type: 'array',
9
+ items: validator,
10
+ name: opts?.name,
11
+ description: opts?.description,
12
+ examples: Array.isArray(validator.examples)
13
+ ? validator.examples.map(v => [v, v])
14
+ : undefined,
15
+ validate: value => {
16
+ if (!Array.isArray(value)) {
17
+ return error([
18
+ {
19
+ code: 'invalid_type',
20
+ message: `Invalid input, expected array, received ${typeof value}`,
21
+ received: typeof value,
22
+ expected: 'array'
23
+ }
24
+ ]);
25
+ }
26
+
27
+ let values: ValidationTypeValue<Validator>[] = [];
28
+
29
+ for (let [key, val] of value.entries()) {
30
+ let result = validator.validate(val);
31
+
32
+ if (!result.success) {
33
+ return error(
34
+ result.errors.map(e => ({ ...e, path: [key.toString(), ...(e.path ?? [])] }))
35
+ );
36
+ }
37
+
38
+ values.push(result.value);
39
+ }
40
+
41
+ return success(values);
42
+ }
43
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { boolean } from './boolean';
3
+
4
+ describe('boolean validator', () => {
5
+ test('should return success for a boolean value', () => {
6
+ let result = boolean({}).validate(true);
7
+ expect(result).toEqual({ success: true, value: true });
8
+ });
9
+
10
+ test('should return an error for a non-boolean value', () => {
11
+ let result = boolean({}).validate('not a boolean');
12
+ expect(result).toEqual({
13
+ success: false,
14
+ errors: [
15
+ {
16
+ code: 'invalid_type',
17
+ message: 'Invalid input, expected a boolean, received string',
18
+ received: 'string',
19
+ expected: 'boolean'
20
+ }
21
+ ]
22
+ });
23
+ });
24
+
25
+ test('should return a custom error message if provided', () => {
26
+ let result = boolean({ message: 'Custom error message' }).validate('not a boolean');
27
+ expect(result).toEqual({
28
+ success: false,
29
+ errors: [
30
+ {
31
+ code: 'invalid_type',
32
+ message: 'Custom error message',
33
+ received: 'string',
34
+ expected: 'boolean'
35
+ }
36
+ ]
37
+ });
38
+ });
39
+ });
@@ -0,0 +1,34 @@
1
+ import { error } from '../lib/result';
2
+ import { ValidatorOptions } from '../lib/types';
3
+ import { createValidator } from '../lib/validator';
4
+
5
+ export let boolean = createValidator<boolean, ValidatorOptions<boolean>>(
6
+ 'boolean',
7
+ (opts, value) => {
8
+ let serializedValue: boolean | undefined;
9
+
10
+ if (typeof value == 'string') {
11
+ if (value == 'true') {
12
+ serializedValue = true;
13
+ } else if (value == 'false') {
14
+ serializedValue = false;
15
+ }
16
+ } else if (typeof value == 'boolean') {
17
+ serializedValue = value;
18
+ }
19
+
20
+ if (serializedValue === undefined) {
21
+ return error([
22
+ {
23
+ code: 'invalid_type',
24
+ message:
25
+ opts.message ?? `Invalid input, expected a boolean, received ${typeof value}`,
26
+ received: typeof value,
27
+ expected: 'boolean'
28
+ }
29
+ ]);
30
+ }
31
+
32
+ return { success: true, value: serializedValue };
33
+ }
34
+ );
@@ -0,0 +1,39 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { date } from './date';
3
+
4
+ describe('date', () => {
5
+ test('should return success for a valid date', () => {
6
+ let input = new Date();
7
+ let result: any = date().validate(input);
8
+ expect(result.success).toBe(true);
9
+ expect(result.value).toBe(input);
10
+ });
11
+
12
+ test('should return an error for an invalid date', () => {
13
+ let input = 'not a date';
14
+ let result: any = date().validate(input);
15
+ expect(result.success).toBe(false);
16
+ expect(result.errors).toEqual([
17
+ {
18
+ code: 'invalid_type',
19
+ message: 'Invalid input, expected a date, received string',
20
+ received: 'string',
21
+ expected: 'date'
22
+ }
23
+ ]);
24
+ });
25
+
26
+ test('should allow custom error messages', () => {
27
+ let input = 'not a date';
28
+ let result: any = date({ message: 'Custom error message' }).validate(input);
29
+ expect(result.success).toBe(false);
30
+ expect(result.errors).toEqual([
31
+ {
32
+ code: 'invalid_type',
33
+ message: 'Custom error message',
34
+ received: 'string',
35
+ expected: 'date'
36
+ }
37
+ ]);
38
+ });
39
+ });
@@ -0,0 +1,26 @@
1
+ import { error } from '../lib/result';
2
+ import { ValidatorOptions } from '../lib/types';
3
+ import { createValidator } from '../lib/validator';
4
+
5
+ export let date = createValidator<Date, ValidatorOptions<Date>>('date', (opts, value) => {
6
+ if (typeof value === 'string') {
7
+ let date = new Date(value);
8
+
9
+ if (!isNaN(date.getTime())) {
10
+ value = date;
11
+ }
12
+ }
13
+
14
+ if (!(value instanceof Date)) {
15
+ return error([
16
+ {
17
+ code: 'invalid_type',
18
+ message: opts.message ?? `Invalid input, expected a date, received ${typeof value}`,
19
+ received: typeof value,
20
+ expected: 'date'
21
+ }
22
+ ]);
23
+ }
24
+
25
+ return { success: true, value };
26
+ });
@@ -0,0 +1,25 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { enumOf } from './enum';
3
+
4
+ describe('enumOf', () => {
5
+ test('should validate a valid value', () => {
6
+ let validator = enumOf(['foo', 'bar', 'baz']);
7
+ let result: any = validator.validate('foo');
8
+ expect(result.success).toBe(true);
9
+ expect(result.value).toBe('foo');
10
+ });
11
+
12
+ test('should not validate an invalid value', () => {
13
+ let validator = enumOf(['foo', 'bar', 'baz']);
14
+ let result: any = validator.validate('qux');
15
+ expect(result.success).toBe(false);
16
+ expect(result.errors).toEqual([
17
+ {
18
+ code: 'invalid_enum',
19
+ message: 'Invalid input, expected one of foo, bar, baz, received qux',
20
+ received: 'qux',
21
+ expected: ['foo', 'bar', 'baz']
22
+ }
23
+ ]);
24
+ });
25
+ });
@@ -0,0 +1,56 @@
1
+ import { error } from '../lib/result';
2
+ import { ValidationType } from '../lib/types';
3
+
4
+ // export let enumType = <
5
+ // A extends string | number | boolean,
6
+ // Rest extends (string | number | boolean)[]
7
+ // >(
8
+ // ...values: [A, ...Rest]
9
+ // ): ValidationType<A | Rest[number]> => ({
10
+ // type: 'enum',
11
+ // examples: values,
12
+ // validate: value => {
13
+ // if (!values.includes(value)) {
14
+ // return error([
15
+ // {
16
+ // code: 'invalid_enum',
17
+ // message: `Invalid input, expected one of ${values.join(', ')}, received ${value}`,
18
+ // received: value,
19
+ // expected: values
20
+ // }
21
+ // ]);
22
+ // }
23
+
24
+ // return { success: true, value };
25
+ // }
26
+ // });
27
+
28
+ export let enumOf = <
29
+ const A extends string | number | boolean,
30
+ Rest extends (string | number | boolean)[]
31
+ >(
32
+ values: [A, ...Rest],
33
+ opts?: {
34
+ name?: string;
35
+ description?: string;
36
+ }
37
+ ): ValidationType<A | Rest[number]> => ({
38
+ type: 'enum',
39
+ examples: values,
40
+ name: opts?.name,
41
+ description: opts?.description,
42
+ validate: value => {
43
+ if (!values.includes(value)) {
44
+ return error([
45
+ {
46
+ code: 'invalid_enum',
47
+ message: `Invalid input, expected one of ${values.join(', ')}, received ${value}`,
48
+ received: value,
49
+ expected: values
50
+ }
51
+ ]);
52
+ }
53
+
54
+ return { success: true, value };
55
+ }
56
+ });
@@ -0,0 +1,16 @@
1
+ export * from './any';
2
+ export * from './array';
3
+ export * from './boolean';
4
+ export * from './date';
5
+ export * from './enum';
6
+ export * from './intersection';
7
+ export * from './literal';
8
+ export * from './null';
9
+ export * from './nullable';
10
+ export * from './number';
11
+ export * from './object';
12
+ export * from './optional';
13
+ export * from './record';
14
+ export * from './string';
15
+ export * from './union';
16
+ export * from './void';
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { success } from '../lib/result';
3
+ import { number, object, string } from './index';
4
+ import { intersection } from './intersection';
5
+
6
+ describe('intersection', () => {
7
+ test('intersection - success', () => {
8
+ const validatorA = object({ name: string() });
9
+ const validatorB = object({ age: number() });
10
+
11
+ const validator = intersection([validatorA, validatorB]);
12
+
13
+ const value = { name: 'John', age: 30, email: 'john@example.com' };
14
+ const result = validator.validate(value);
15
+
16
+ expect(result).toEqual(
17
+ success({
18
+ name: 'John',
19
+ age: 30
20
+ })
21
+ );
22
+ });
23
+
24
+ test('intersection - failure', () => {
25
+ const validatorA = object({ name: string() });
26
+ const validatorB = object({ age: number() });
27
+
28
+ const validator = intersection([validatorA, validatorB]);
29
+
30
+ const value = { name: 'John', age: 'test', email: 'john@example.com' };
31
+ const result = validator.validate(value);
32
+
33
+ expect(result.success).toBe(false);
34
+ });
35
+ });
@@ -0,0 +1,24 @@
1
+ import { ValidationType, ValidationTypeValue, ValidatorOptions } from '../lib/types';
2
+
3
+ export let intersection = <A extends ValidationType<object>, B extends ValidationType<object>>(
4
+ validators: [A, B],
5
+ opts?: ValidatorOptions<ValidationTypeValue<A> & ValidationTypeValue<B>>
6
+ ): ValidationType<ValidationTypeValue<A> & ValidationTypeValue<B>> => ({
7
+ type: 'intersection',
8
+ name: opts?.name,
9
+ items: validators,
10
+ description: opts?.description,
11
+ examples: [],
12
+ validate: value => {
13
+ let fullValue: any = {};
14
+
15
+ for (let validator of validators) {
16
+ let result = validator.validate(value);
17
+ if (!result.success) return result;
18
+
19
+ fullValue = { ...fullValue, ...result.value };
20
+ }
21
+
22
+ return { success: true, value: fullValue };
23
+ }
24
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { literal } from './literal';
3
+
4
+ describe('literal', () => {
5
+ test('should return success when the value matches the literal', () => {
6
+ let validator = literal('foo');
7
+ let result = validator.validate('foo');
8
+ expect(result).toEqual({ success: true, value: 'foo' });
9
+ });
10
+
11
+ test('should return an error when the value does not match the literal', () => {
12
+ let validator = literal('foo');
13
+ let result = validator.validate('bar');
14
+ expect(result).toEqual({
15
+ success: false,
16
+ errors: [
17
+ {
18
+ code: 'invalid_literal',
19
+ message: 'Invalid input, expected foo, received bar',
20
+ received: 'bar',
21
+ expected: 'foo'
22
+ }
23
+ ]
24
+ });
25
+ });
26
+
27
+ test('should use the provided error message', () => {
28
+ let validator = literal('foo', { message: 'Custom error message' });
29
+ let result = validator.validate('bar');
30
+ expect(result).toEqual({
31
+ success: false,
32
+ errors: [
33
+ {
34
+ code: 'invalid_literal',
35
+ message: 'Custom error message',
36
+ received: 'bar',
37
+ expected: 'foo'
38
+ }
39
+ ]
40
+ });
41
+ });
42
+ });
@@ -0,0 +1,26 @@
1
+ import { error } from '../lib/result';
2
+ import { ValidationType, ValidatorOptions } from '../lib/types';
3
+
4
+ export let literal = <A extends string | number | boolean>(
5
+ value: A,
6
+ opts?: ValidatorOptions<A>
7
+ ): ValidationType<A> => ({
8
+ type: 'literal',
9
+ examples: [value],
10
+ name: opts?.name,
11
+ description: opts?.description,
12
+ validate: input => {
13
+ if (input != value) {
14
+ return error([
15
+ {
16
+ code: 'invalid_literal',
17
+ message: opts?.message ?? `Invalid input, expected ${value}, received ${input}`,
18
+ received: input,
19
+ expected: value
20
+ }
21
+ ]);
22
+ }
23
+
24
+ return { success: true, value };
25
+ }
26
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { nullValue } from './null';
3
+
4
+ describe('null', () => {
5
+ test('should return success for valid null', () => {
6
+ let result = nullValue({}).validate(null);
7
+ expect(result).toEqual({ success: true, value: null });
8
+ });
9
+
10
+ test('should return an error for an invalid null', () => {
11
+ let result = nullValue({}).validate('not a null');
12
+ expect(result).toEqual({
13
+ success: false,
14
+ errors: [
15
+ {
16
+ code: 'invalid_type',
17
+ message: 'Invalid input, expected null, received string',
18
+ received: 'string',
19
+ expected: 'null'
20
+ }
21
+ ]
22
+ });
23
+ });
24
+
25
+ test('should use the provided error message', () => {
26
+ let result = nullValue({ message: 'Custom error message' }).validate('not a null');
27
+ expect(result).toEqual({
28
+ success: false,
29
+ errors: [
30
+ {
31
+ code: 'invalid_type',
32
+ message: 'Custom error message',
33
+ received: 'string',
34
+ expected: 'null'
35
+ }
36
+ ]
37
+ });
38
+ });
39
+ });
@@ -0,0 +1,18 @@
1
+ import { error } from '../lib/result';
2
+ import { ValidatorOptions } from '../lib/types';
3
+ import { createValidator } from '../lib/validator';
4
+
5
+ export let nullValue = createValidator<null, ValidatorOptions<null>>('null', (opts, value) => {
6
+ if (value !== null) {
7
+ return error([
8
+ {
9
+ code: 'invalid_type',
10
+ message: opts.message ?? `Invalid input, expected null, received ${typeof value}`,
11
+ received: typeof value,
12
+ expected: 'null'
13
+ }
14
+ ]);
15
+ }
16
+
17
+ return { success: true, value: null };
18
+ });
@@ -0,0 +1,34 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { nullable } from './nullable';
3
+ import { string } from './string';
4
+
5
+ describe('nullable', () => {
6
+ test('should return a validator that allows null values', () => {
7
+ let validator = nullable(string());
8
+
9
+ expect(validator.validate(null)).toEqual({ success: true, value: null });
10
+ });
11
+
12
+ test('should return a validator that delegates to the input validator for non-null values', () => {
13
+ let validator = nullable(string());
14
+
15
+ expect(validator.validate('hello')).toEqual({ success: true, value: 'hello' });
16
+ expect(validator.validate(42)).toEqual({
17
+ success: false,
18
+ errors: [
19
+ {
20
+ code: 'invalid_type',
21
+ message: 'Invalid input, expected string, received number',
22
+ received: 'number',
23
+ expected: 'string'
24
+ }
25
+ ]
26
+ });
27
+ });
28
+
29
+ test('should return a validator with the correct type and optional flag', () => {
30
+ let validator = nullable(string());
31
+
32
+ expect(validator.type).toBe('string');
33
+ });
34
+ });