Skip to content

Commit 1191e93

Browse files
committed
fix: array run validator before sanitizer, fix instanceof, add tests for api
1 parent 9274a37 commit 1191e93

20 files changed

+622
-178
lines changed

src/api/arrays.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export class VArray<I> extends VCore<I[]> {
66
constructor (comparer: VCore<I>, err?: string) {
77
super()
88
this.addTyping(isArray(err))
9-
this.addRule(makeRule<I[]>((value) => {
9+
this.addTyping(makeRule<I[]>((value) => {
1010
const mapped = (value as I[] ?? []).reduce((acc, cur) => {
1111
const comp = comparer.parse(cur)
1212
acc[0].push(comp.value)

src/api/base.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class VBase<I> {
5151
const typeCheck = check(val, group.typings, group.options)
5252
if (!typeCheck.valid) return typeCheck
5353

54-
const sanitizedValue = this.#sanitize(val, group.sanitizers)
54+
const sanitizedValue = this.#sanitize(typeCheck.value, group.sanitizers)
5555
const v = check(sanitizedValue, group.rules, group.options)
5656
if (!v.valid) return v
5757

src/api/index.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import { VTuple } from './tuples'
1616
export { VCore }
1717

1818
export const v = {
19-
is: <T = any> (value: T) => new VCore<T>().eq(value),
20-
in: <T = any> (values: Readonly<T[]>, comparer = Differ.equal as (val: any, arrayItem: T) => boolean) => new VCore<T>().in(values, comparer),
19+
is: <T> (value: T) => new VCore<T>().eq(value),
20+
in: <T> (values: Readonly<T[]>, comparer = Differ.equal as (val: any, arrayItem: T) => boolean) => new VCore<T>().in(values, comparer),
2121
or: VBase.createType(VOr),
2222
and: VBase.createType(VAnd),
2323
string: VBase.createType(VString),
@@ -30,9 +30,9 @@ export const v = {
3030
object: VBase.createType(VObject),
3131
record: VBase.createType(VRecord),
3232
map: VBase.createType(VMap),
33-
null: (err?: string) => new VCore<null>().addRule((val) => isNull(err)(val)),
34-
undefined: (err?: string) => new VCore<undefined>().addRule((val) => isUndefined(err)(val)),
35-
instanceof: <T> (classDef: new () => T, err?: string) => new VCore<T>().addRule((val) => isInstanceOf(classDef, err)(val)),
33+
null: (err?: string) => new VCore<null>().addRule(isNull(err)),
34+
undefined: (err?: string) => new VCore<undefined>().addRule(isUndefined(err)),
35+
instanceof: <T> (classDef: new () => T, err?: string) => new VCore<T>().addRule(isInstanceOf(classDef, err)),
3636
any: <T = any> () => new VCore<T>(),
3737
force: {
3838
string: VBase.createForcedType<VString, string, ConstructorParameters<typeof VString>>(VString, (v) => String(v)),

src/api/records.ts

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export class VRecord<VI> extends VCore<Record<string, VI>> {
2828
constructor (vCom: VCore<VI>, err?: string) {
2929
super()
3030
this.addTyping(makeRule<Record<string, VI>>((value) => {
31+
if (Array.isArray(value)) return isInvalid(err ? [err] : ['is not an object'], value)
3132
const val = structuredClone(value) as Record<string, VI>
3233
for (const [k, v] of Object.entries(val ?? {})) {
3334
const validity = vCom.parse(v as any)

src/rules/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const isBoolean = (error = 'is not a boolean') => makeRule<boolean>((valu
2121
export const isInstanceOf = <T> (classDef: new () => T, error?: string) => makeRule<T>((value) => {
2222
error = error ?? `is not an instance of ${classDef.name}`
2323
const val = value as T
24+
if ((val as any)?.constructor === classDef) return isValid(val)
2425
if (value instanceof classDef) return isValid(val)
2526
return isInvalid([error], val)
2627
})

src/utils/functions/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const trimToLength = (body: string, length: number) => {
1818
if (body === null || body === undefined) return body
1919

2020
body = body.toString()
21-
if (body.length < length) return body
21+
if (body.length <= length) return body
2222

2323
const indexOfSpace = body.indexOf(' ', length)
2424
const indexToTrim = indexOfSpace === -1 ? length : indexOfSpace

src/utils/regexes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import url from 'url-regex-safe'
22

3-
export const urlRegex = url()
3+
export const urlRegex: RegExp = url()
44
export const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/

tests/api/arrays.test.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { v } from '../../src/api'
3+
4+
describe('array', () => {
5+
test('array', () => {
6+
const rules = v.array(v.string())
7+
expect(rules.parse([]).valid).toBe(true)
8+
expect(rules.parse(['']).valid).toBe(true)
9+
expect(rules.parse([2]).valid).toBe(false)
10+
})
11+
12+
test('has', () => {
13+
const rules = v.array(v.any()).has(2)
14+
expect(rules.parse([1]).valid).toBe(false)
15+
expect(rules.parse([1, 2]).valid).toBe(true)
16+
expect(rules.parse([1, 2, 3]).valid).toBe(false)
17+
})
18+
19+
test('min', () => {
20+
const rules = v.array(v.any()).min(2)
21+
expect(rules.parse([1]).valid).toBe(false)
22+
expect(rules.parse([1, 2]).valid).toBe(true)
23+
expect(rules.parse([1, 2, 3]).valid).toBe(true)
24+
})
25+
26+
test('max', () => {
27+
const rules = v.array(v.any()).max(2)
28+
expect(rules.parse([1]).valid).toBe(true)
29+
expect(rules.parse([1, 2]).valid).toBe(true)
30+
expect(rules.parse([1, 2, 3]).valid).toBe(false)
31+
})
32+
33+
test('set', () => {
34+
const rules = v.array(v.number().round()).set()
35+
expect(rules.parse([1]).value).toEqual([1])
36+
expect(rules.parse([1, 1]).value).toEqual([1])
37+
expect(rules.parse([1, 2, 3]).value).toEqual([1, 2, 3])
38+
expect(rules.parse([1.1, 1.2, 1.3]).value).toEqual([1])
39+
})
40+
})

tests/api/booleans.test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { v } from '../../src/api'
3+
4+
describe('boolean', () => {
5+
test('boolean', () => {
6+
const rules = v.boolean()
7+
expect(rules.parse(true).valid).toBe(true)
8+
expect(rules.parse(false).valid).toBe(true)
9+
expect(rules.parse([2]).valid).toBe(false)
10+
})
11+
})

tests/api/files.test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { v } from '../../src/api'
3+
4+
describe('file', () => {
5+
test('file', () => {
6+
const rules = v.file()
7+
expect(rules.parse({ type: 'image/png' }).valid).toBe(true)
8+
expect(rules.parse({ type: '' }).valid).toBe(false)
9+
expect(rules.parse(false).valid).toBe(false)
10+
})
11+
12+
test('image', () => {
13+
const rules = v.file().image()
14+
expect(rules.parse({ type: 'image/png' }).valid).toBe(true)
15+
expect(rules.parse({ type: '' }).valid).toBe(false)
16+
expect(rules.parse(false).valid).toBe(false)
17+
})
18+
19+
test('video', () => {
20+
const rules = v.file().video()
21+
expect(rules.parse({ type: 'video/mp4' }).valid).toBe(true)
22+
expect(rules.parse({ type: '' }).valid).toBe(false)
23+
expect(rules.parse(false).valid).toBe(false)
24+
})
25+
26+
test('audio', () => {
27+
const rules = v.file().audio()
28+
expect(rules.parse({ type: 'audio/mp3' }).valid).toBe(true)
29+
expect(rules.parse({ type: '' }).valid).toBe(false)
30+
expect(rules.parse(false).valid).toBe(false)
31+
})
32+
})

tests/api/index.test.ts

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { v } from '../../src/api'
3+
4+
describe('index', () => {
5+
test('is', () => {
6+
const rules = v.is(1)
7+
expect(rules.parse('1').valid).toBe(false)
8+
expect(rules.parse(2).valid).toBe(false)
9+
expect(rules.parse(true).valid).toBe(false)
10+
expect(rules.parse(1).valid).toBe(true)
11+
})
12+
13+
test('in', () => {
14+
const rules = v.in([1, 2, 3])
15+
expect(rules.parse('1').valid).toBe(false)
16+
expect(rules.parse(4).valid).toBe(false)
17+
expect(rules.parse(true).valid).toBe(false)
18+
expect(rules.parse(1).valid).toBe(true)
19+
})
20+
21+
test('null', () => {
22+
const rules = v.null()
23+
expect(rules.parse('1').valid).toBe(false)
24+
expect(rules.parse(4).valid).toBe(false)
25+
expect(rules.parse(true).valid).toBe(false)
26+
expect(rules.parse(null).valid).toBe(true)
27+
})
28+
29+
test('undefined', () => {
30+
const rules = v.undefined()
31+
expect(rules.parse('1').valid).toBe(false)
32+
expect(rules.parse(4).valid).toBe(false)
33+
expect(rules.parse(true).valid).toBe(false)
34+
expect(rules.parse(undefined).valid).toBe(true)
35+
})
36+
37+
test('any', () => {
38+
const rules = v.any()
39+
expect(rules.parse('1').valid).toBe(true)
40+
expect(rules.parse(4).valid).toBe(true)
41+
expect(rules.parse(true).valid).toBe(true)
42+
expect(rules.parse(undefined).valid).toBe(true)
43+
})
44+
45+
test('instanceof', () => {
46+
const rules = v.instanceof(String)
47+
expect(rules.parse('abc').valid).toBe(true)
48+
expect(rules.parse(4).valid).toBe(false)
49+
expect(rules.parse(true).valid).toBe(false)
50+
expect(rules.parse(undefined).valid).toBe(false)
51+
})
52+
})
53+
54+
describe('force', () => {
55+
test('string', () => {
56+
const rules = v.force.string()
57+
expect(rules.parse('').valid).toBe(true)
58+
expect(rules.parse(2).valid).toBe(true)
59+
expect(rules.parse(false).valid).toBe(true)
60+
expect(rules.parse({}).valid).toBe(true)
61+
})
62+
63+
test('number', () => {
64+
const rules = v.force.number()
65+
expect(rules.parse('23').valid).toBe(true)
66+
expect(rules.parse(2).valid).toBe(true)
67+
expect(rules.parse(false).valid).toBe(true)
68+
expect(rules.parse({}).valid).toBe(false)
69+
})
70+
71+
test('boolean', () => {
72+
const rules = v.force.boolean()
73+
expect(rules.parse('23').valid).toBe(true)
74+
expect(rules.parse(2).valid).toBe(true)
75+
expect(rules.parse(false).valid).toBe(true)
76+
expect(rules.parse({}).valid).toBe(true)
77+
})
78+
79+
test('time', () => {
80+
const rules = v.force.time()
81+
expect(rules.parse('2023').valid).toBe(true)
82+
expect(rules.parse(2).valid).toBe(true)
83+
expect(rules.parse(false).valid).toBe(true)
84+
expect(rules.parse({}).valid).toBe(false)
85+
})
86+
})

tests/api/junctions.test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { v } from '../../src/api'
3+
4+
describe('or', () => {
5+
test('or', () => {
6+
const rules = v.or([
7+
v.string(),
8+
v.number(),
9+
])
10+
expect(rules.parse('').valid).toBe(true)
11+
expect(rules.parse(2).valid).toBe(true)
12+
expect(rules.parse(false).valid).toBe(false)
13+
})
14+
})
15+
16+
describe('and', () => {
17+
test('and', () => {
18+
const rules = v.and([
19+
v.string(),
20+
v.any().eq('and'),
21+
])
22+
expect(rules.parse('and').valid).toBe(true)
23+
expect(rules.parse('').valid).toBe(false)
24+
expect(rules.parse(false).valid).toBe(false)
25+
})
26+
})

tests/api/numbers.test.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { v } from '../../src/api'
3+
4+
describe('number', () => {
5+
test('number', () => {
6+
const rules = v.number()
7+
expect(rules.parse('2').valid).toBe(false)
8+
expect(rules.parse(2).valid).toBe(true)
9+
expect(rules.parse(false).valid).toBe(false)
10+
})
11+
12+
test('lt', () => {
13+
const rules = v.number().lt(3)
14+
expect(rules.parse(3).valid).toBe(false)
15+
expect(rules.parse(2).valid).toBe(true)
16+
expect(rules.parse(4).valid).toBe(false)
17+
})
18+
19+
test('lte', () => {
20+
const rules = v.number().lte(3)
21+
expect(rules.parse(3).valid).toBe(true)
22+
expect(rules.parse(2).valid).toBe(true)
23+
expect(rules.parse(4).valid).toBe(false)
24+
})
25+
26+
test('gt', () => {
27+
const rules = v.number().gt(3)
28+
expect(rules.parse(3).valid).toBe(false)
29+
expect(rules.parse(4).valid).toBe(true)
30+
expect(rules.parse(2).valid).toBe(false)
31+
})
32+
33+
test('lte', () => {
34+
const rules = v.number().gte(3)
35+
expect(rules.parse(3).valid).toBe(true)
36+
expect(rules.parse(4).valid).toBe(true)
37+
expect(rules.parse(2).valid).toBe(false)
38+
})
39+
40+
test('int', () => {
41+
const rules = v.number().int()
42+
expect(rules.parse(3.6).valid).toBe(false)
43+
expect(rules.parse(3).valid).toBe(true)
44+
})
45+
46+
test('round', () => {
47+
const rules = v.number().round(3)
48+
expect(rules.parse(3.45678).value).toEqual(3.457)
49+
expect(rules.parse(4).value).toEqual(4)
50+
})
51+
})

tests/api/objects.test.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { v } from '../../src/api'
3+
4+
describe('objects', () => {
5+
test('object', () => {
6+
const rules = v.object({
7+
name: v.string()
8+
})
9+
expect(rules.parse([]).valid).toBe(false)
10+
expect(rules.parse('').valid).toBe(false)
11+
expect(rules.parse({}).valid).toBe(false)
12+
expect(rules.parse({ name: 1 }).valid).toBe(false)
13+
expect(rules.parse({ name: '' }).valid).toBe(true)
14+
15+
let res = rules.parse({ name: '', age: 23 })
16+
expect(res.valid).toBe(true)
17+
expect(res.value).toEqual({ name: '' })
18+
19+
res = v.object({
20+
name: v.string()
21+
}, false).parse({ name: '', age: 23 })
22+
expect(res.valid).toBe(true)
23+
expect(res.value).toEqual({ name: '', age: 23 })
24+
})
25+
})

tests/api/records.test.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { describe, expect, test } from 'vitest'
2+
import { v } from '../../src/api'
3+
4+
describe('maps', () => {
5+
test('map', () => {
6+
const rules = v.map(v.string(), v.number())
7+
expect(rules.parse([]).valid).toBe(false)
8+
expect(rules.parse({}).valid).toBe(false)
9+
expect(rules.parse(new Map([['', '']])).valid).toBe(false)
10+
expect(rules.parse(new Map([])).valid).toBe(true)
11+
expect(rules.parse(new Map([['', 1]])).valid).toBe(true)
12+
})
13+
})
14+
15+
describe('records', () => {
16+
test('record', () => {
17+
const rules = v.record(v.number())
18+
expect(rules.parse([]).valid).toBe(false)
19+
expect(rules.parse({}).valid).toBe(true)
20+
expect(rules.parse({ a: 'a' }).valid).toBe(false)
21+
expect(rules.parse({ a: 1 }).valid).toBe(true)
22+
})
23+
})

0 commit comments

Comments
 (0)