@atproto/lex-schema 0.1.4 → 0.1.6
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.
- package/CHANGELOG.md +24 -0
- package/dist/core/$type.d.ts +2 -2
- package/dist/core/$type.d.ts.map +1 -1
- package/dist/core/$type.js.map +1 -1
- package/dist/core/record-key.d.ts +1 -1
- package/dist/core/record-key.d.ts.map +1 -1
- package/dist/core/record-key.js.map +1 -1
- package/dist/core/schema.d.ts +3 -2
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +1 -1
- package/dist/core/schema.js.map +1 -1
- package/dist/core/standard-schema.d.ts +2 -2
- package/dist/core/standard-schema.d.ts.map +1 -1
- package/dist/core/standard-schema.js.map +1 -1
- package/dist/core/string-format.d.ts +2 -2
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validation-error.d.ts +1 -1
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +1 -1
- package/dist/core/validation-error.js.map +1 -1
- package/dist/core/validator.d.ts +1 -1
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +1 -1
- package/dist/core/validator.js.map +1 -1
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +2 -2
- package/dist/helpers.js.map +1 -1
- package/dist/schema/array.d.ts +1 -1
- package/dist/schema/array.d.ts.map +1 -1
- package/dist/schema/array.js +1 -1
- package/dist/schema/array.js.map +1 -1
- package/dist/schema/blob.d.ts +1 -1
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +2 -2
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/boolean.js +1 -1
- package/dist/schema/boolean.js.map +1 -1
- package/dist/schema/bytes.js +1 -1
- package/dist/schema/bytes.js.map +1 -1
- package/dist/schema/cid.d.ts +1 -1
- package/dist/schema/cid.d.ts.map +1 -1
- package/dist/schema/cid.js +3 -3
- package/dist/schema/cid.js.map +1 -1
- package/dist/schema/custom.js +1 -1
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/dict.d.ts +1 -1
- package/dist/schema/dict.d.ts.map +1 -1
- package/dist/schema/dict.js +1 -1
- package/dist/schema/dict.js.map +1 -1
- package/dist/schema/discriminated-union.d.ts +1 -1
- package/dist/schema/discriminated-union.d.ts.map +1 -1
- package/dist/schema/discriminated-union.js +2 -1
- package/dist/schema/discriminated-union.js.map +1 -1
- package/dist/schema/enum.js +1 -1
- package/dist/schema/enum.js.map +1 -1
- package/dist/schema/integer.js +1 -1
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/intersection.d.ts +1 -1
- package/dist/schema/intersection.d.ts.map +1 -1
- package/dist/schema/intersection.js +3 -1
- package/dist/schema/intersection.js.map +1 -1
- package/dist/schema/lex-map.d.ts +1 -1
- package/dist/schema/lex-map.d.ts.map +1 -1
- package/dist/schema/lex-map.js +1 -1
- package/dist/schema/lex-map.js.map +1 -1
- package/dist/schema/lex-value.d.ts +1 -1
- package/dist/schema/lex-value.d.ts.map +1 -1
- package/dist/schema/lex-value.js +1 -1
- package/dist/schema/lex-value.js.map +1 -1
- package/dist/schema/literal.js +1 -1
- package/dist/schema/literal.js.map +1 -1
- package/dist/schema/never.js +1 -1
- package/dist/schema/never.js.map +1 -1
- package/dist/schema/null.js +1 -1
- package/dist/schema/null.js.map +1 -1
- package/dist/schema/nullable.d.ts +1 -1
- package/dist/schema/nullable.d.ts.map +1 -1
- package/dist/schema/nullable.js +1 -1
- package/dist/schema/nullable.js.map +1 -1
- package/dist/schema/object.d.ts +2 -1
- package/dist/schema/object.d.ts.map +1 -1
- package/dist/schema/object.js +1 -1
- package/dist/schema/object.js.map +1 -1
- package/dist/schema/optional.d.ts +2 -1
- package/dist/schema/optional.d.ts.map +1 -1
- package/dist/schema/optional.js +2 -1
- package/dist/schema/optional.js.map +1 -1
- package/dist/schema/params.d.ts +1 -1
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +1 -1
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts +3 -2
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js +2 -1
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/permission-set.d.ts +1 -1
- package/dist/schema/permission-set.d.ts.map +1 -1
- package/dist/schema/permission-set.js +1 -0
- package/dist/schema/permission-set.js.map +1 -1
- package/dist/schema/permission.d.ts +1 -1
- package/dist/schema/permission.d.ts.map +1 -1
- package/dist/schema/permission.js.map +1 -1
- package/dist/schema/procedure.d.ts +1 -1
- package/dist/schema/procedure.d.ts.map +1 -1
- package/dist/schema/procedure.js +2 -0
- package/dist/schema/procedure.js.map +1 -1
- package/dist/schema/query.d.ts +1 -1
- package/dist/schema/query.d.ts.map +1 -1
- package/dist/schema/query.js +2 -0
- package/dist/schema/query.js.map +1 -1
- package/dist/schema/record.d.ts +2 -2
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +1 -1
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/ref.d.ts +1 -1
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js +1 -1
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/refine.d.ts +2 -2
- package/dist/schema/refine.d.ts.map +1 -1
- package/dist/schema/refine.js +1 -1
- package/dist/schema/refine.js.map +1 -1
- package/dist/schema/regexp.js +1 -1
- package/dist/schema/regexp.js.map +1 -1
- package/dist/schema/string.d.ts +2 -2
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +1 -1
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/subscription.d.ts +3 -2
- package/dist/schema/subscription.d.ts.map +1 -1
- package/dist/schema/subscription.js +2 -0
- package/dist/schema/subscription.js.map +1 -1
- package/dist/schema/token.d.ts +1 -1
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +1 -1
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-object.d.ts +2 -2
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +1 -1
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +1 -1
- package/dist/schema/typed-ref.d.ts.map +1 -1
- package/dist/schema/typed-ref.js +1 -1
- package/dist/schema/typed-ref.js.map +1 -1
- package/dist/schema/typed-union.d.ts +1 -1
- package/dist/schema/typed-union.d.ts.map +1 -1
- package/dist/schema/typed-union.js +3 -1
- package/dist/schema/typed-union.js.map +1 -1
- package/dist/schema/union.d.ts +1 -1
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +1 -1
- package/dist/schema/union.js.map +1 -1
- package/dist/schema/unknown.js +1 -1
- package/dist/schema/unknown.js.map +1 -1
- package/dist/schema/with-default.d.ts +1 -1
- package/dist/schema/with-default.d.ts.map +1 -1
- package/dist/schema/with-default.js +1 -1
- package/dist/schema/with-default.js.map +1 -1
- package/package.json +6 -10
- package/src/core/$type.test.ts +0 -24
- package/src/core/$type.ts +0 -199
- package/src/core/record-key.ts +0 -85
- package/src/core/result.ts +0 -15
- package/src/core/schema.ts +0 -412
- package/src/core/standard-schema.test.ts +0 -124
- package/src/core/standard-schema.ts +0 -31
- package/src/core/string-format.ts +0 -411
- package/src/core/types.ts +0 -120
- package/src/core/validation-error.ts +0 -134
- package/src/core/validation-issue.ts +0 -340
- package/src/core/validator.ts +0 -636
- package/src/core.ts +0 -9
- package/src/external.ts +0 -3
- package/src/helpers.test.ts +0 -694
- package/src/helpers.ts +0 -222
- package/src/index.ts +0 -3
- package/src/schema/array.test.ts +0 -251
- package/src/schema/array.ts +0 -126
- package/src/schema/blob.test.ts +0 -733
- package/src/schema/blob.ts +0 -150
- package/src/schema/boolean.test.ts +0 -118
- package/src/schema/boolean.ts +0 -46
- package/src/schema/bytes.test.ts +0 -227
- package/src/schema/bytes.ts +0 -81
- package/src/schema/cid.test.ts +0 -125
- package/src/schema/cid.ts +0 -69
- package/src/schema/custom.test.ts +0 -414
- package/src/schema/custom.ts +0 -106
- package/src/schema/dict.test.ts +0 -181
- package/src/schema/dict.ts +0 -122
- package/src/schema/discriminated-union.test.ts +0 -676
- package/src/schema/discriminated-union.ts +0 -196
- package/src/schema/enum.test.ts +0 -398
- package/src/schema/enum.ts +0 -77
- package/src/schema/integer.test.ts +0 -314
- package/src/schema/integer.ts +0 -86
- package/src/schema/intersection.test.ts +0 -33
- package/src/schema/intersection.ts +0 -113
- package/src/schema/lex-map.test.ts +0 -593
- package/src/schema/lex-map.ts +0 -63
- package/src/schema/lex-value.test.ts +0 -81
- package/src/schema/lex-value.ts +0 -86
- package/src/schema/literal.test.ts +0 -533
- package/src/schema/literal.ts +0 -70
- package/src/schema/never.test.ts +0 -175
- package/src/schema/never.ts +0 -56
- package/src/schema/null.test.ts +0 -80
- package/src/schema/null.ts +0 -49
- package/src/schema/nullable.test.ts +0 -470
- package/src/schema/nullable.ts +0 -74
- package/src/schema/object.test.ts +0 -69
- package/src/schema/object.ts +0 -136
- package/src/schema/optional.test.ts +0 -479
- package/src/schema/optional.ts +0 -92
- package/src/schema/params.test.ts +0 -1118
- package/src/schema/params.ts +0 -371
- package/src/schema/payload.test.ts +0 -340
- package/src/schema/payload.ts +0 -204
- package/src/schema/permission-set.test.ts +0 -613
- package/src/schema/permission-set.ts +0 -86
- package/src/schema/permission.test.ts +0 -537
- package/src/schema/permission.ts +0 -63
- package/src/schema/procedure.test.ts +0 -324
- package/src/schema/procedure.ts +0 -98
- package/src/schema/query.test.ts +0 -348
- package/src/schema/query.ts +0 -86
- package/src/schema/record.test.ts +0 -812
- package/src/schema/record.ts +0 -217
- package/src/schema/ref.test.ts +0 -349
- package/src/schema/ref.ts +0 -103
- package/src/schema/refine.test.ts +0 -579
- package/src/schema/refine.ts +0 -153
- package/src/schema/regexp.test.ts +0 -577
- package/src/schema/regexp.ts +0 -82
- package/src/schema/string.test.ts +0 -773
- package/src/schema/string.ts +0 -229
- package/src/schema/subscription.test.ts +0 -499
- package/src/schema/subscription.ts +0 -108
- package/src/schema/token.test.ts +0 -152
- package/src/schema/token.ts +0 -103
- package/src/schema/typed-object.test.ts +0 -745
- package/src/schema/typed-object.ts +0 -181
- package/src/schema/typed-ref.test.ts +0 -796
- package/src/schema/typed-ref.ts +0 -126
- package/src/schema/typed-union.test.ts +0 -355
- package/src/schema/typed-union.ts +0 -130
- package/src/schema/union.test.ts +0 -191
- package/src/schema/union.ts +0 -89
- package/src/schema/unknown.test.ts +0 -313
- package/src/schema/unknown.ts +0 -47
- package/src/schema/with-default.ts +0 -81
- package/src/schema.ts +0 -43
- package/src/util/array-agg.test.ts +0 -42
- package/src/util/array-agg.ts +0 -44
- package/src/util/assertion-util.ts +0 -1
- package/src/util/if-any.ts +0 -3
- package/src/util/lazy-property.ts +0 -14
- package/src/util/memoize.ts +0 -37
- package/tsconfig.build.json +0 -12
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -8
package/src/schema/cid.test.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { parseCid } from '@atproto/lex-data'
|
|
3
|
-
import { cid } from './cid.js'
|
|
4
|
-
|
|
5
|
-
const cborCid = parseCid(
|
|
6
|
-
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
7
|
-
{ flavor: 'cbor' },
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
const rawCid = parseCid(
|
|
11
|
-
'bafkreifjjcie6lypi6ny7amxnfftagclbuxndqonfipmb64f2km2devei4',
|
|
12
|
-
{ flavor: 'raw' },
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
const v0Cid = parseCid('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')
|
|
16
|
-
|
|
17
|
-
// Using git-raw codec (0x78) instead of DAG-CBOR or raw binary
|
|
18
|
-
const gitRawCid = parseCid(
|
|
19
|
-
'bafybeigvgzoolc3drupxhlevdp2ugqcrbcsqfmcek2zxiw5wctk3xjpjwy',
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
// Using SHA-512 (0x13) instead of SHA-256
|
|
23
|
-
const sha512Cid = parseCid(
|
|
24
|
-
'bafybgqfcn3rz4mdzywp2jb6mjvpdq24rxjvbmdcmizrjdgx2ujjpvj4kxf4d62ywrzm6njk44cxhha4pj3bkvqz2esfgrm7mdkdcqcxjibf7c',
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
describe('CidSchema', () => {
|
|
28
|
-
describe('default mode (non-strict)', () => {
|
|
29
|
-
const schema = cid({})
|
|
30
|
-
|
|
31
|
-
it('validates CID v1 with DAG-CBOR codec and SHA-256', () => {
|
|
32
|
-
const result = schema.safeParse(cborCid)
|
|
33
|
-
expect(result.success).toBe(true)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('validates CID v1 with raw binary codec', () => {
|
|
37
|
-
const result = schema.safeParse(rawCid)
|
|
38
|
-
expect(result.success).toBe(true)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('validates CID v0', () => {
|
|
42
|
-
const result = schema.safeParse(v0Cid)
|
|
43
|
-
expect(result.success).toBe(true)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('rejects non-CID objects', () => {
|
|
47
|
-
const result = schema.safeParse({ not: 'a cid' })
|
|
48
|
-
expect(result.success).toBe(false)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('rejects strings', () => {
|
|
52
|
-
const result = schema.safeParse(cborCid.toString())
|
|
53
|
-
expect(result.success).toBe(false)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('rejects numbers', () => {
|
|
57
|
-
const result = schema.safeParse(123)
|
|
58
|
-
expect(result.success).toBe(false)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('rejects null', () => {
|
|
62
|
-
const result = schema.safeParse(null)
|
|
63
|
-
expect(result.success).toBe(false)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('rejects undefined', () => {
|
|
67
|
-
const result = schema.safeParse(undefined)
|
|
68
|
-
expect(result.success).toBe(false)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('rejects arrays', () => {
|
|
72
|
-
const result = schema.safeParse([])
|
|
73
|
-
expect(result.success).toBe(false)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('rejects booleans', () => {
|
|
77
|
-
const result = schema.safeParse(true)
|
|
78
|
-
expect(result.success).toBe(false)
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
describe('strict mode', () => {
|
|
83
|
-
const schema = cid({ flavor: 'dasl' })
|
|
84
|
-
|
|
85
|
-
it('validates CID v1 with DAG-CBOR codec and SHA-256', () => {
|
|
86
|
-
const result = schema.safeParse(cborCid)
|
|
87
|
-
expect(result.success).toBe(true)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('validates CID v1 with raw binary codec and SHA-256', () => {
|
|
91
|
-
const result = schema.safeParse(rawCid)
|
|
92
|
-
expect(result.success).toBe(true)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('rejects CID v0', () => {
|
|
96
|
-
const result = schema.safeParse(v0Cid)
|
|
97
|
-
expect(result.success).toBe(false)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('rejects CID v1 with non-standard codec', () => {
|
|
101
|
-
const result = schema.safeParse(gitRawCid)
|
|
102
|
-
expect(result.success).toBe(false)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('rejects CID v1 with non-SHA-256 hash', () => {
|
|
106
|
-
const result = schema.safeParse(sha512Cid)
|
|
107
|
-
expect(result.success).toBe(false)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('rejects non-CID objects', () => {
|
|
111
|
-
const result = schema.safeParse({ not: 'a cid' })
|
|
112
|
-
expect(result.success).toBe(false)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('rejects strings', () => {
|
|
116
|
-
const result = schema.safeParse(cborCid.toString())
|
|
117
|
-
expect(result.success).toBe(false)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('rejects null', () => {
|
|
121
|
-
const result = schema.safeParse(null)
|
|
122
|
-
expect(result.success).toBe(false)
|
|
123
|
-
})
|
|
124
|
-
})
|
|
125
|
-
})
|
package/src/schema/cid.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { CheckCidOptions, Cid, InferCheckedCid, isCid } from '@atproto/lex-data'
|
|
2
|
-
import { Schema, ValidationContext } from '../core.js'
|
|
3
|
-
import { memoizedOptions } from '../util/memoize.js'
|
|
4
|
-
|
|
5
|
-
export type { Cid }
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Configuration options for CID schema validation.
|
|
9
|
-
*
|
|
10
|
-
* @see CheckCidOptions from @atproto/lex-data for available options
|
|
11
|
-
*/
|
|
12
|
-
export type CidSchemaOptions = CheckCidOptions
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Schema for validating Content Identifiers (CIDs).
|
|
16
|
-
*
|
|
17
|
-
* CIDs are self-describing content-addressed identifiers used in AT Protocol
|
|
18
|
-
* to reference data by its cryptographic hash. This schema validates that
|
|
19
|
-
* the input is a valid CID object.
|
|
20
|
-
*
|
|
21
|
-
* @template TOptions - The configuration options type
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* const schema = new CidSchema()
|
|
26
|
-
* const result = schema.validate(someCid)
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export class CidSchema<
|
|
30
|
-
const TOptions extends CidSchemaOptions = { flavor: undefined },
|
|
31
|
-
> extends Schema<InferCheckedCid<TOptions>> {
|
|
32
|
-
readonly type = 'cid' as const
|
|
33
|
-
|
|
34
|
-
constructor(readonly options?: TOptions) {
|
|
35
|
-
super()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
39
|
-
if (!isCid(input, this.options)) {
|
|
40
|
-
return ctx.issueUnexpectedType(input, 'cid')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return ctx.success(input)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Creates a CID schema for validating Content Identifiers.
|
|
49
|
-
*
|
|
50
|
-
* CIDs are used throughout AT Protocol to reference content by its hash.
|
|
51
|
-
* This is commonly used for referencing blobs, commits, and other data.
|
|
52
|
-
*
|
|
53
|
-
* @param options - Optional configuration for CID validation
|
|
54
|
-
* @returns A new {@link CidSchema} instance
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```ts
|
|
58
|
-
* // Basic CID validation
|
|
59
|
-
* const cidSchema = l.cid()
|
|
60
|
-
*
|
|
61
|
-
* // Validate a CID from a blob reference
|
|
62
|
-
* const result = cidSchema.validate(blobRef.ref)
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
export const cid = /*#__PURE__*/ memoizedOptions(function <
|
|
66
|
-
O extends CidSchemaOptions = NonNullable<unknown>,
|
|
67
|
-
>(options?: O) {
|
|
68
|
-
return new CidSchema(options)
|
|
69
|
-
})
|
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import { IssueCustom } from '../core.js'
|
|
3
|
-
import { custom } from './custom.js'
|
|
4
|
-
|
|
5
|
-
describe('CustomSchema', () => {
|
|
6
|
-
describe('basic validation', () => {
|
|
7
|
-
it('validates input that passes custom assertion', () => {
|
|
8
|
-
const schema = custom(
|
|
9
|
-
(input): input is string => typeof input === 'string',
|
|
10
|
-
'Must be a string',
|
|
11
|
-
)
|
|
12
|
-
const result = schema.safeParse('hello')
|
|
13
|
-
expect(result.success).toBe(true)
|
|
14
|
-
if (result.success) {
|
|
15
|
-
expect(result.value).toBe('hello')
|
|
16
|
-
}
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('rejects input that fails custom assertion', () => {
|
|
20
|
-
const schema = custom(
|
|
21
|
-
(input): input is string => typeof input === 'string',
|
|
22
|
-
'Must be a string',
|
|
23
|
-
)
|
|
24
|
-
const result = schema.safeParse(123)
|
|
25
|
-
expect(result.success).toBe(false)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('includes custom message in error', () => {
|
|
29
|
-
const schema = custom(
|
|
30
|
-
(input): input is string => typeof input === 'string',
|
|
31
|
-
'Custom error message',
|
|
32
|
-
)
|
|
33
|
-
const result = schema.safeParse(123)
|
|
34
|
-
expect(result.success).toBe(false)
|
|
35
|
-
if (!result.success) {
|
|
36
|
-
expect(result.reason.message).toContain('Custom error message')
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
describe('complex type guards', () => {
|
|
42
|
-
it('validates objects with specific properties', () => {
|
|
43
|
-
interface User {
|
|
44
|
-
name: string
|
|
45
|
-
age: number
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const schema = custom((input): input is User => {
|
|
49
|
-
return (
|
|
50
|
-
typeof input === 'object' &&
|
|
51
|
-
input !== null &&
|
|
52
|
-
'name' in input &&
|
|
53
|
-
'age' in input &&
|
|
54
|
-
typeof (input as any).name === 'string' &&
|
|
55
|
-
typeof (input as any).age === 'number'
|
|
56
|
-
)
|
|
57
|
-
}, 'Must be a valid User object')
|
|
58
|
-
|
|
59
|
-
expect(schema.matches({ name: 'Alice', age: 30 })).toBe(true)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('rejects objects missing required properties', () => {
|
|
63
|
-
interface User {
|
|
64
|
-
name: string
|
|
65
|
-
age: number
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const schema = custom((input): input is User => {
|
|
69
|
-
return (
|
|
70
|
-
typeof input === 'object' &&
|
|
71
|
-
input !== null &&
|
|
72
|
-
'name' in input &&
|
|
73
|
-
'age' in input &&
|
|
74
|
-
typeof (input as any).name === 'string' &&
|
|
75
|
-
typeof (input as any).age === 'number'
|
|
76
|
-
)
|
|
77
|
-
}, 'Must be a valid User object')
|
|
78
|
-
|
|
79
|
-
expect(schema.matches({ name: 'Alice' })).toBe(false)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('validates arrays with specific element types', () => {
|
|
83
|
-
const schema = custom((input): input is number[] => {
|
|
84
|
-
return (
|
|
85
|
-
Array.isArray(input) &&
|
|
86
|
-
input.every((item) => typeof item === 'number')
|
|
87
|
-
)
|
|
88
|
-
}, 'Must be an array of numbers')
|
|
89
|
-
|
|
90
|
-
const result = schema.safeParse([1, 2, 3, 4])
|
|
91
|
-
expect(result.success).toBe(true)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('rejects arrays with mixed types', () => {
|
|
95
|
-
const schema = custom((input): input is number[] => {
|
|
96
|
-
return (
|
|
97
|
-
Array.isArray(input) &&
|
|
98
|
-
input.every((item) => typeof item === 'number')
|
|
99
|
-
)
|
|
100
|
-
}, 'Must be an array of numbers')
|
|
101
|
-
|
|
102
|
-
const result = schema.safeParse([1, 'two', 3])
|
|
103
|
-
expect(result.success).toBe(false)
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
describe('custom context usage', () => {
|
|
108
|
-
it('can add custom issues through context', () => {
|
|
109
|
-
const schema = custom((input, ctx): input is string => {
|
|
110
|
-
if (typeof input !== 'string') {
|
|
111
|
-
ctx.addIssue({
|
|
112
|
-
code: 'invalid_type',
|
|
113
|
-
path: ctx.path,
|
|
114
|
-
input,
|
|
115
|
-
expected: ['string'],
|
|
116
|
-
} as any)
|
|
117
|
-
return false
|
|
118
|
-
}
|
|
119
|
-
return true
|
|
120
|
-
}, 'Must be a string')
|
|
121
|
-
|
|
122
|
-
const result = schema.safeParse(123)
|
|
123
|
-
expect(result.success).toBe(false)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('accesses path from context', () => {
|
|
127
|
-
let capturedPath: any[] = []
|
|
128
|
-
const schema = custom((input, ctx): input is string => {
|
|
129
|
-
capturedPath = [...ctx.path]
|
|
130
|
-
return typeof input === 'string'
|
|
131
|
-
}, 'Must be a string')
|
|
132
|
-
|
|
133
|
-
schema.safeParse('test')
|
|
134
|
-
expect(capturedPath).toEqual([])
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('validates with custom path', () => {
|
|
138
|
-
const schema = custom(
|
|
139
|
-
(input): input is string => typeof input === 'string',
|
|
140
|
-
'Must be a string',
|
|
141
|
-
'customField',
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
const result = schema.safeParse(123)
|
|
145
|
-
expect(result.success).toBe(false)
|
|
146
|
-
if (!result.success) {
|
|
147
|
-
expect(result.reason.message).toContain('customField')
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('validates with array of paths', () => {
|
|
152
|
-
const schema = custom(
|
|
153
|
-
(input): input is string => typeof input === 'string',
|
|
154
|
-
'Must be a string',
|
|
155
|
-
['nested', 'field'],
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
const result = schema.safeParse(123)
|
|
159
|
-
expect(result.success).toBe(false)
|
|
160
|
-
if (!result.success) {
|
|
161
|
-
expect(result.reason.message).toContain('nested')
|
|
162
|
-
expect(result.reason.message).toContain('field')
|
|
163
|
-
}
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
describe('business logic validation', () => {
|
|
168
|
-
it('validates email format', () => {
|
|
169
|
-
const schema = custom((input): input is string => {
|
|
170
|
-
return (
|
|
171
|
-
typeof input === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)
|
|
172
|
-
)
|
|
173
|
-
}, 'Must be a valid email address')
|
|
174
|
-
|
|
175
|
-
const validResult = schema.safeParse('user@example.com')
|
|
176
|
-
expect(validResult.success).toBe(true)
|
|
177
|
-
|
|
178
|
-
const invalidResult = schema.safeParse('not-an-email')
|
|
179
|
-
expect(invalidResult.success).toBe(false)
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it('validates password strength', () => {
|
|
183
|
-
const schema = custom((input): input is string => {
|
|
184
|
-
if (typeof input !== 'string') return false
|
|
185
|
-
return (
|
|
186
|
-
input.length >= 8 &&
|
|
187
|
-
/[A-Z]/.test(input) &&
|
|
188
|
-
/[a-z]/.test(input) &&
|
|
189
|
-
/[0-9]/.test(input)
|
|
190
|
-
)
|
|
191
|
-
}, 'Password must be at least 8 characters with uppercase, lowercase, and numbers')
|
|
192
|
-
|
|
193
|
-
const validResult = schema.safeParse('MyPass123')
|
|
194
|
-
expect(validResult.success).toBe(true)
|
|
195
|
-
|
|
196
|
-
const weakResult = schema.safeParse('weak')
|
|
197
|
-
expect(weakResult.success).toBe(false)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('validates age range', () => {
|
|
201
|
-
const schema = custom((input): input is number => {
|
|
202
|
-
return typeof input === 'number' && input >= 18 && input <= 120
|
|
203
|
-
}, 'Age must be between 18 and 120')
|
|
204
|
-
|
|
205
|
-
const validResult = schema.safeParse(25)
|
|
206
|
-
expect(validResult.success).toBe(true)
|
|
207
|
-
|
|
208
|
-
const tooYoungResult = schema.safeParse(15)
|
|
209
|
-
expect(tooYoungResult.success).toBe(false)
|
|
210
|
-
|
|
211
|
-
const tooOldResult = schema.safeParse(150)
|
|
212
|
-
expect(tooOldResult.success).toBe(false)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('validates positive numbers', () => {
|
|
216
|
-
const schema = custom((input): input is number => {
|
|
217
|
-
return typeof input === 'number' && input > 0
|
|
218
|
-
}, 'Must be a positive number')
|
|
219
|
-
|
|
220
|
-
const validResult = schema.safeParse(5)
|
|
221
|
-
expect(validResult.success).toBe(true)
|
|
222
|
-
|
|
223
|
-
const zeroResult = schema.safeParse(0)
|
|
224
|
-
expect(zeroResult.success).toBe(false)
|
|
225
|
-
|
|
226
|
-
const negativeResult = schema.safeParse(-5)
|
|
227
|
-
expect(negativeResult.success).toBe(false)
|
|
228
|
-
})
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
describe('edge cases', () => {
|
|
232
|
-
it('handles null input', () => {
|
|
233
|
-
const schema = custom(
|
|
234
|
-
(input): input is null => input === null,
|
|
235
|
-
'Must be null',
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
const validResult = schema.safeParse(null)
|
|
239
|
-
expect(validResult.success).toBe(true)
|
|
240
|
-
|
|
241
|
-
const invalidResult = schema.safeParse(undefined)
|
|
242
|
-
expect(invalidResult.success).toBe(false)
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('handles undefined input', () => {
|
|
246
|
-
const schema = custom(
|
|
247
|
-
(input): input is undefined => input === undefined,
|
|
248
|
-
'Must be undefined',
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
const validResult = schema.safeParse(undefined)
|
|
252
|
-
expect(validResult.success).toBe(true)
|
|
253
|
-
|
|
254
|
-
const invalidResult = schema.safeParse(null)
|
|
255
|
-
expect(invalidResult.success).toBe(false)
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
it('handles empty string', () => {
|
|
259
|
-
const schema = custom(
|
|
260
|
-
(input): input is string =>
|
|
261
|
-
typeof input === 'string' && input.length > 0,
|
|
262
|
-
'Must be a non-empty string',
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
const validResult = schema.safeParse('hello')
|
|
266
|
-
expect(validResult.success).toBe(true)
|
|
267
|
-
|
|
268
|
-
const invalidResult = schema.safeParse('')
|
|
269
|
-
expect(invalidResult.success).toBe(false)
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
it('handles empty array', () => {
|
|
273
|
-
const schema = custom(
|
|
274
|
-
(input): input is any[] => Array.isArray(input) && input.length > 0,
|
|
275
|
-
'Must be a non-empty array',
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
const validResult = schema.safeParse([1, 2, 3])
|
|
279
|
-
expect(validResult.success).toBe(true)
|
|
280
|
-
|
|
281
|
-
const invalidResult = schema.safeParse([])
|
|
282
|
-
expect(invalidResult.success).toBe(false)
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
it('handles complex nested structures', () => {
|
|
286
|
-
interface ComplexType {
|
|
287
|
-
users: Array<{ name: string; email: string }>
|
|
288
|
-
metadata: { count: number }
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const schema = custom((input): input is ComplexType => {
|
|
292
|
-
if (typeof input !== 'object' || input === null) return false
|
|
293
|
-
const obj = input as any
|
|
294
|
-
return (
|
|
295
|
-
Array.isArray(obj.users) &&
|
|
296
|
-
obj.users.every(
|
|
297
|
-
(u: any) =>
|
|
298
|
-
typeof u === 'object' &&
|
|
299
|
-
typeof u.name === 'string' &&
|
|
300
|
-
typeof u.email === 'string',
|
|
301
|
-
) &&
|
|
302
|
-
typeof obj.metadata === 'object' &&
|
|
303
|
-
typeof obj.metadata.count === 'number'
|
|
304
|
-
)
|
|
305
|
-
}, 'Must be a valid complex structure')
|
|
306
|
-
|
|
307
|
-
const validResult = schema.safeParse({
|
|
308
|
-
users: [
|
|
309
|
-
{ name: 'Alice', email: 'alice@example.com' },
|
|
310
|
-
{ name: 'Bob', email: 'bob@example.com' },
|
|
311
|
-
],
|
|
312
|
-
metadata: { count: 2 },
|
|
313
|
-
})
|
|
314
|
-
expect(validResult.success).toBe(true)
|
|
315
|
-
|
|
316
|
-
const invalidResult = schema.safeParse({
|
|
317
|
-
users: [{ name: 'Alice' }], // missing email
|
|
318
|
-
metadata: { count: 1 },
|
|
319
|
-
})
|
|
320
|
-
expect(invalidResult.success).toBe(false)
|
|
321
|
-
})
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
describe('type narrowing', () => {
|
|
325
|
-
it('correctly narrows union types', () => {
|
|
326
|
-
type StringOrNumber = string | number
|
|
327
|
-
|
|
328
|
-
const schema = custom(
|
|
329
|
-
(input): input is string => typeof input === 'string',
|
|
330
|
-
'Must be a string',
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
const input: StringOrNumber = 'hello'
|
|
334
|
-
|
|
335
|
-
const result = schema.safeParse(input)
|
|
336
|
-
expect(result.success).toBe(true)
|
|
337
|
-
|
|
338
|
-
if (result.success) {
|
|
339
|
-
// Type should be narrowed to string
|
|
340
|
-
const value: string = result.value
|
|
341
|
-
expect(typeof value).toBe('string')
|
|
342
|
-
}
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
it('validates discriminated unions', () => {
|
|
346
|
-
type Shape =
|
|
347
|
-
| { type: 'circle'; radius: number }
|
|
348
|
-
| { type: 'rectangle'; width: number; height: number }
|
|
349
|
-
|
|
350
|
-
const circleSchema = custom((input): input is Shape => {
|
|
351
|
-
return (
|
|
352
|
-
typeof input === 'object' &&
|
|
353
|
-
input !== null &&
|
|
354
|
-
'type' in input &&
|
|
355
|
-
(input as any).type === 'circle' &&
|
|
356
|
-
'radius' in input &&
|
|
357
|
-
typeof (input as any).radius === 'number'
|
|
358
|
-
)
|
|
359
|
-
}, 'Must be a valid circle')
|
|
360
|
-
|
|
361
|
-
const validResult = circleSchema.safeParse({ type: 'circle', radius: 5 })
|
|
362
|
-
expect(validResult.success).toBe(true)
|
|
363
|
-
|
|
364
|
-
const invalidResult = circleSchema.safeParse({
|
|
365
|
-
type: 'rectangle',
|
|
366
|
-
width: 10,
|
|
367
|
-
height: 20,
|
|
368
|
-
})
|
|
369
|
-
expect(invalidResult.success).toBe(false)
|
|
370
|
-
})
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
describe('assertion context behavior', () => {
|
|
374
|
-
it('calls assertion with null as this', () => {
|
|
375
|
-
const assertion = vi.fn(function (
|
|
376
|
-
this: unknown,
|
|
377
|
-
input: unknown,
|
|
378
|
-
): input is string {
|
|
379
|
-
expect(this).toBeNull()
|
|
380
|
-
return typeof input === 'string'
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
custom(assertion as any, 'Must be a string').safeParse('test')
|
|
384
|
-
|
|
385
|
-
expect(assertion).toHaveBeenCalledTimes(1)
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
it('provides addIssue method in context', () => {
|
|
389
|
-
const schema = custom((input, ctx): input is string => {
|
|
390
|
-
ctx.addIssue(new IssueCustom(ctx.path, input, 'This is a custom issue'))
|
|
391
|
-
return false
|
|
392
|
-
}, 'Must be a string')
|
|
393
|
-
|
|
394
|
-
expect(schema.safeParse('test')).toMatchObject({
|
|
395
|
-
success: false,
|
|
396
|
-
reason: {
|
|
397
|
-
issues: [
|
|
398
|
-
{ message: 'This is a custom issue' },
|
|
399
|
-
{ message: 'Must be a string' },
|
|
400
|
-
],
|
|
401
|
-
},
|
|
402
|
-
})
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
it('provides path array in context', () => {
|
|
406
|
-
const schema = custom((input, ctx): input is string => {
|
|
407
|
-
expect(Array.isArray(ctx.path)).toBe(true)
|
|
408
|
-
return typeof input === 'string'
|
|
409
|
-
}, 'Must be a string')
|
|
410
|
-
|
|
411
|
-
schema.safeParse('test')
|
|
412
|
-
})
|
|
413
|
-
})
|
|
414
|
-
})
|
package/src/schema/custom.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { Issue, IssueCustom, Schema, ValidationContext } from '../core.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Context object provided to custom assertion functions.
|
|
5
|
-
*
|
|
6
|
-
* @property path - Current validation path as an array of property keys
|
|
7
|
-
* @property addIssue - Function to add additional validation issues
|
|
8
|
-
*/
|
|
9
|
-
export type CustomAssertionContext = {
|
|
10
|
-
path: PropertyKey[]
|
|
11
|
-
addIssue(issue: Issue): void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Type guard function for custom schema validation.
|
|
16
|
-
*
|
|
17
|
-
* @template TValue - The type to validate/narrow to
|
|
18
|
-
*/
|
|
19
|
-
export type CustomAssertion<TValue> = (
|
|
20
|
-
this: null,
|
|
21
|
-
input: unknown,
|
|
22
|
-
ctx: CustomAssertionContext,
|
|
23
|
-
) => input is TValue
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Schema with a custom validation function.
|
|
27
|
-
*
|
|
28
|
-
* Allows defining completely custom validation logic using a type guard
|
|
29
|
-
* assertion function. The function receives the input and validation context,
|
|
30
|
-
* and must return whether the input is valid.
|
|
31
|
-
*
|
|
32
|
-
* @template TValue - The validated output type
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* const schema = new CustomSchema(
|
|
37
|
-
* (input): input is Date => input instanceof Date,
|
|
38
|
-
* 'Expected a Date instance'
|
|
39
|
-
* )
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
export class CustomSchema<out TValue = unknown> extends Schema<TValue> {
|
|
43
|
-
readonly type = 'custom' as const
|
|
44
|
-
|
|
45
|
-
constructor(
|
|
46
|
-
private readonly assertion: CustomAssertion<TValue>,
|
|
47
|
-
private readonly message: string,
|
|
48
|
-
private readonly path?: PropertyKey | readonly PropertyKey[],
|
|
49
|
-
) {
|
|
50
|
-
super()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
54
|
-
if (!this.assertion.call(null, input, ctx)) {
|
|
55
|
-
const path = ctx.concatPath(this.path)
|
|
56
|
-
return ctx.issue(new IssueCustom(path, input, this.message))
|
|
57
|
-
}
|
|
58
|
-
return ctx.success(input as TValue)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Creates a custom schema with a user-defined validation function.
|
|
64
|
-
*
|
|
65
|
-
* Use this when the built-in schemas don't cover your validation needs.
|
|
66
|
-
* The assertion function must be a type guard that narrows the input type.
|
|
67
|
-
*
|
|
68
|
-
* @param assertion - Type guard function that validates the input
|
|
69
|
-
* @param message - Error message when validation fails
|
|
70
|
-
* @param path - Optional path to associate with validation errors
|
|
71
|
-
* @returns A new {@link CustomSchema} instance
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```ts
|
|
75
|
-
* // Validate Date instances
|
|
76
|
-
* const dateSchema = l.custom(
|
|
77
|
-
* (input): input is Date => input instanceof Date && !isNaN(input.getTime()),
|
|
78
|
-
* 'Expected a valid Date'
|
|
79
|
-
* )
|
|
80
|
-
*
|
|
81
|
-
* // Validate specific object shape
|
|
82
|
-
* const pointSchema = l.custom(
|
|
83
|
-
* (input): input is { x: number; y: number } =>
|
|
84
|
-
* typeof input === 'object' &&
|
|
85
|
-
* input !== null &&
|
|
86
|
-
* typeof (input as any).x === 'number' &&
|
|
87
|
-
* typeof (input as any).y === 'number',
|
|
88
|
-
* 'Expected a point with x and y coordinates'
|
|
89
|
-
* )
|
|
90
|
-
*
|
|
91
|
-
* // With custom path
|
|
92
|
-
* const validConfig = l.custom(
|
|
93
|
-
* (input): input is Config => validateConfig(input),
|
|
94
|
-
* 'Invalid configuration',
|
|
95
|
-
* ['config']
|
|
96
|
-
* )
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
/*@__NO_SIDE_EFFECTS__*/
|
|
100
|
-
export function custom<TValue>(
|
|
101
|
-
assertion: CustomAssertion<TValue>,
|
|
102
|
-
message: string,
|
|
103
|
-
path?: PropertyKey | readonly PropertyKey[],
|
|
104
|
-
) {
|
|
105
|
-
return new CustomSchema<TValue>(assertion, message, path)
|
|
106
|
-
}
|