@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/object.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { isPlainObject } from '@atproto/lex-data'
|
|
2
|
-
import {
|
|
3
|
-
InferInput,
|
|
4
|
-
InferOutput,
|
|
5
|
-
Schema,
|
|
6
|
-
ValidationContext,
|
|
7
|
-
Validator,
|
|
8
|
-
WithOptionalProperties,
|
|
9
|
-
} from '../core.js'
|
|
10
|
-
import { lazyProperty } from '../util/lazy-property.js'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Type representing the shape of an object schema.
|
|
14
|
-
*
|
|
15
|
-
* Maps property names to their corresponding validators.
|
|
16
|
-
*/
|
|
17
|
-
export type ObjectSchemaShape = Record<string, Validator>
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Schema for validating objects with a defined shape.
|
|
21
|
-
*
|
|
22
|
-
* Each property in the shape is validated against its corresponding schema.
|
|
23
|
-
* Properties wrapped in `optional()` are not required.
|
|
24
|
-
*
|
|
25
|
-
* @template TShape - The object shape type mapping property names to validators
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```ts
|
|
29
|
-
* const schema = new ObjectSchema({
|
|
30
|
-
* name: l.string(),
|
|
31
|
-
* age: l.optional(l.integer()),
|
|
32
|
-
* })
|
|
33
|
-
* const result = schema.validate({ name: 'Alice' })
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
export class ObjectSchema<
|
|
37
|
-
const TShape extends ObjectSchemaShape = any,
|
|
38
|
-
> extends Schema<
|
|
39
|
-
WithOptionalProperties<{
|
|
40
|
-
[K in keyof TShape]: InferInput<TShape[K]>
|
|
41
|
-
}>,
|
|
42
|
-
WithOptionalProperties<{
|
|
43
|
-
[K in keyof TShape]: InferOutput<TShape[K]>
|
|
44
|
-
}>
|
|
45
|
-
> {
|
|
46
|
-
readonly type = 'object' as const
|
|
47
|
-
|
|
48
|
-
constructor(readonly shape: TShape) {
|
|
49
|
-
super()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
get validatorsMap(): Map<string, Validator> {
|
|
53
|
-
const map = new Map(Object.entries(this.shape))
|
|
54
|
-
|
|
55
|
-
return lazyProperty(this, 'validatorsMap', map)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
59
|
-
if (!isPlainObject(input)) {
|
|
60
|
-
return ctx.issueUnexpectedType(input, 'object')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Lazily copy value
|
|
64
|
-
let copy: undefined | Record<string, unknown>
|
|
65
|
-
|
|
66
|
-
for (const [key, propDef] of this.validatorsMap) {
|
|
67
|
-
const result = ctx.validateChild(input, key, propDef)
|
|
68
|
-
if (!result.success) {
|
|
69
|
-
if (!(key in input)) {
|
|
70
|
-
// Transform into "required key" issue
|
|
71
|
-
return ctx.issueRequiredKey(input, key)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return result
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Skip copying if key is not present in input (and value is undefined)
|
|
78
|
-
if (result.value === undefined && !(key in input)) {
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!Object.is(result.value, input[key])) {
|
|
83
|
-
if (ctx.options.mode === 'validate') {
|
|
84
|
-
// In "validate" mode, we can't modify the input, so we issue an error
|
|
85
|
-
return ctx.issueInvalidPropertyValue(input, key, [result.value])
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
copy ??= { ...input }
|
|
89
|
-
copy[key] = result.value
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return ctx.success(copy ?? input)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Creates an object schema with the specified property validators.
|
|
99
|
-
*
|
|
100
|
-
* Validates that the input is a plain object and each property matches
|
|
101
|
-
* its corresponding schema. Properties wrapped in `optional()` are not required.
|
|
102
|
-
*
|
|
103
|
-
* @param properties - Object mapping property names to their validators
|
|
104
|
-
* @returns A new {@link ObjectSchema} instance
|
|
105
|
-
*
|
|
106
|
-
* @example
|
|
107
|
-
* ```ts
|
|
108
|
-
* // Basic object
|
|
109
|
-
* const userSchema = l.object({
|
|
110
|
-
* name: l.string(),
|
|
111
|
-
* email: l.string({ format: 'uri' }),
|
|
112
|
-
* })
|
|
113
|
-
*
|
|
114
|
-
* // With optional properties
|
|
115
|
-
* const profileSchema = l.object({
|
|
116
|
-
* displayName: l.string(),
|
|
117
|
-
* bio: l.optional(l.string({ maxLength: 256 })),
|
|
118
|
-
* avatar: l.optional(l.blob({ accept: ['image/*'] })),
|
|
119
|
-
* })
|
|
120
|
-
*
|
|
121
|
-
* // Nested objects
|
|
122
|
-
* const postSchema = l.object({
|
|
123
|
-
* text: l.string(),
|
|
124
|
-
* author: l.object({
|
|
125
|
-
* did: l.string({ format: 'did' }),
|
|
126
|
-
* handle: l.string({ format: 'handle' }),
|
|
127
|
-
* }),
|
|
128
|
-
* })
|
|
129
|
-
* ```
|
|
130
|
-
*/
|
|
131
|
-
/*@__NO_SIDE_EFFECTS__*/
|
|
132
|
-
export function object<const TShape extends ObjectSchemaShape>(
|
|
133
|
-
properties: TShape,
|
|
134
|
-
) {
|
|
135
|
-
return new ObjectSchema<TShape>(properties)
|
|
136
|
-
}
|
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { boolean } from './boolean.js'
|
|
3
|
-
import { integer } from './integer.js'
|
|
4
|
-
import { optional } from './optional.js'
|
|
5
|
-
import { string } from './string.js'
|
|
6
|
-
import { withDefault } from './with-default.js'
|
|
7
|
-
|
|
8
|
-
describe('OptionalSchema', () => {
|
|
9
|
-
describe('basic validation with string schema', () => {
|
|
10
|
-
const schema = optional(string())
|
|
11
|
-
|
|
12
|
-
it('validates defined string values', () => {
|
|
13
|
-
const result = schema.safeParse('hello')
|
|
14
|
-
expect(result.success).toBe(true)
|
|
15
|
-
if (result.success) {
|
|
16
|
-
expect(result.value).toBe('hello')
|
|
17
|
-
}
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('validates empty strings', () => {
|
|
21
|
-
const result = schema.safeParse('')
|
|
22
|
-
expect(result.success).toBe(true)
|
|
23
|
-
if (result.success) {
|
|
24
|
-
expect(result.value).toBe('')
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('validates undefined', () => {
|
|
29
|
-
const result = schema.safeParse(undefined)
|
|
30
|
-
expect(result.success).toBe(true)
|
|
31
|
-
if (result.success) {
|
|
32
|
-
expect(result.value).toBe(undefined)
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('rejects invalid types for the inner schema', () => {
|
|
37
|
-
const result = schema.safeParse(123)
|
|
38
|
-
expect(result.success).toBe(false)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('rejects null', () => {
|
|
42
|
-
const result = schema.safeParse(null)
|
|
43
|
-
expect(result.success).toBe(false)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('rejects booleans', () => {
|
|
47
|
-
const result = schema.safeParse(true)
|
|
48
|
-
expect(result.success).toBe(false)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('rejects objects', () => {
|
|
52
|
-
const result = schema.safeParse({ value: 'hello' })
|
|
53
|
-
expect(result.success).toBe(false)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('rejects arrays', () => {
|
|
57
|
-
const result = schema.safeParse(['hello'])
|
|
58
|
-
expect(result.success).toBe(false)
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
describe('basic validation with integer schema', () => {
|
|
63
|
-
const schema = optional(integer())
|
|
64
|
-
|
|
65
|
-
it('validates defined integer values', () => {
|
|
66
|
-
const result = schema.safeParse(42)
|
|
67
|
-
expect(result.success).toBe(true)
|
|
68
|
-
if (result.success) {
|
|
69
|
-
expect(result.value).toBe(42)
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('validates zero', () => {
|
|
74
|
-
const result = schema.safeParse(0)
|
|
75
|
-
expect(result.success).toBe(true)
|
|
76
|
-
if (result.success) {
|
|
77
|
-
expect(result.value).toBe(0)
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('validates negative integers', () => {
|
|
82
|
-
const result = schema.safeParse(-42)
|
|
83
|
-
expect(result.success).toBe(true)
|
|
84
|
-
if (result.success) {
|
|
85
|
-
expect(result.value).toBe(-42)
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
it('validates undefined', () => {
|
|
90
|
-
const result = schema.safeParse(undefined)
|
|
91
|
-
expect(result.success).toBe(true)
|
|
92
|
-
if (result.success) {
|
|
93
|
-
expect(result.value).toBe(undefined)
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('rejects invalid types for the inner schema', () => {
|
|
98
|
-
const result = schema.safeParse('not a number')
|
|
99
|
-
expect(result.success).toBe(false)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('rejects floats', () => {
|
|
103
|
-
const result = schema.safeParse(3.14)
|
|
104
|
-
expect(result.success).toBe(false)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('rejects null', () => {
|
|
108
|
-
const result = schema.safeParse(null)
|
|
109
|
-
expect(result.success).toBe(false)
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
describe('basic validation with boolean schema', () => {
|
|
114
|
-
const schema = optional(boolean())
|
|
115
|
-
|
|
116
|
-
it('validates true', () => {
|
|
117
|
-
const result = schema.safeParse(true)
|
|
118
|
-
expect(result.success).toBe(true)
|
|
119
|
-
if (result.success) {
|
|
120
|
-
expect(result.value).toBe(true)
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('validates false', () => {
|
|
125
|
-
const result = schema.safeParse(false)
|
|
126
|
-
expect(result.success).toBe(true)
|
|
127
|
-
if (result.success) {
|
|
128
|
-
expect(result.value).toBe(false)
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('validates undefined', () => {
|
|
133
|
-
const result = schema.safeParse(undefined)
|
|
134
|
-
expect(result.success).toBe(true)
|
|
135
|
-
if (result.success) {
|
|
136
|
-
expect(result.value).toBe(undefined)
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
it('rejects strings', () => {
|
|
141
|
-
const result = schema.safeParse('true')
|
|
142
|
-
expect(result.success).toBe(false)
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
it('rejects numbers', () => {
|
|
146
|
-
const result = schema.safeParse(1)
|
|
147
|
-
expect(result.success).toBe(false)
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
it('rejects null', () => {
|
|
151
|
-
const result = schema.safeParse(null)
|
|
152
|
-
expect(result.success).toBe(false)
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
describe('inner schema with constraints', () => {
|
|
157
|
-
const schema = optional(string({ minLength: 5, maxLength: 10 }))
|
|
158
|
-
|
|
159
|
-
it('validates values meeting inner schema constraints', () => {
|
|
160
|
-
const result = schema.safeParse('hello')
|
|
161
|
-
expect(result.success).toBe(true)
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('validates values at minimum boundary', () => {
|
|
165
|
-
const result = schema.safeParse('abcde')
|
|
166
|
-
expect(result.success).toBe(true)
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
it('validates values at maximum boundary', () => {
|
|
170
|
-
const result = schema.safeParse('1234567890')
|
|
171
|
-
expect(result.success).toBe(true)
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('validates undefined', () => {
|
|
175
|
-
const result = schema.safeParse(undefined)
|
|
176
|
-
expect(result.success).toBe(true)
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('rejects values violating inner schema minimum constraint', () => {
|
|
180
|
-
const result = schema.safeParse('hi')
|
|
181
|
-
expect(result.success).toBe(false)
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('rejects values violating inner schema maximum constraint', () => {
|
|
185
|
-
const result = schema.safeParse('this is too long')
|
|
186
|
-
expect(result.success).toBe(false)
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
it('rejects empty strings when inner schema has minLength', () => {
|
|
190
|
-
const result = schema.safeParse('')
|
|
191
|
-
expect(result.success).toBe(false)
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
describe('inner schema with default value', () => {
|
|
196
|
-
const schema = optional(withDefault(string(), 'default'))
|
|
197
|
-
|
|
198
|
-
it('applies default value when undefined is provided', () => {
|
|
199
|
-
const result = schema.safeParse(undefined)
|
|
200
|
-
expect(result.success).toBe(true)
|
|
201
|
-
if (result.success) {
|
|
202
|
-
expect(result.value).toBe('default')
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
it('does not apply default when explicit value is provided', () => {
|
|
207
|
-
const result = schema.safeParse('explicit')
|
|
208
|
-
expect(result.success).toBe(true)
|
|
209
|
-
if (result.success) {
|
|
210
|
-
expect(result.value).toBe('explicit')
|
|
211
|
-
}
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
it('does not apply default when empty string is provided', () => {
|
|
215
|
-
const result = schema.safeParse('')
|
|
216
|
-
expect(result.success).toBe(true)
|
|
217
|
-
if (result.success) {
|
|
218
|
-
expect(result.value).toBe('')
|
|
219
|
-
}
|
|
220
|
-
})
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
describe('inner schema with default value and constraints', () => {
|
|
224
|
-
const schema = optional(withDefault(string({ minLength: 5 }), 'default'))
|
|
225
|
-
|
|
226
|
-
it('applies default value when undefined is provided', () => {
|
|
227
|
-
const result = schema.safeParse(undefined)
|
|
228
|
-
expect(result.success).toBe(true)
|
|
229
|
-
if (result.success) {
|
|
230
|
-
expect(result.value).toBe('default')
|
|
231
|
-
}
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
it('validates explicit values against constraints', () => {
|
|
235
|
-
const result = schema.safeParse('hello')
|
|
236
|
-
expect(result.success).toBe(true)
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('rejects explicit values violating constraints', () => {
|
|
240
|
-
const result = schema.safeParse('hi')
|
|
241
|
-
expect(result.success).toBe(false)
|
|
242
|
-
})
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
describe('inner schema with invalid default value', () => {
|
|
246
|
-
const schema = optional(string({ default: 'bad', minLength: 5 }))
|
|
247
|
-
|
|
248
|
-
it('returns undefined when default value violates constraints', () => {
|
|
249
|
-
const result = schema.safeParse(undefined)
|
|
250
|
-
expect(result.success).toBe(true)
|
|
251
|
-
if (result.success) {
|
|
252
|
-
expect(result.value).toBe(undefined)
|
|
253
|
-
}
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
it('still validates conforming explicit values', () => {
|
|
257
|
-
const result = schema.safeParse('valid')
|
|
258
|
-
expect(result.success).toBe(true)
|
|
259
|
-
})
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
describe('inner schema with integer default', () => {
|
|
263
|
-
const schema = optional(withDefault(integer(), 42))
|
|
264
|
-
|
|
265
|
-
it('applies default value when undefined is provided', () => {
|
|
266
|
-
const result = schema.safeParse(undefined)
|
|
267
|
-
expect(result.success).toBe(true)
|
|
268
|
-
if (result.success) {
|
|
269
|
-
expect(result.value).toBe(42)
|
|
270
|
-
}
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
it('does not apply default when explicit value is provided', () => {
|
|
274
|
-
const result = schema.safeParse(100)
|
|
275
|
-
expect(result.success).toBe(true)
|
|
276
|
-
if (result.success) {
|
|
277
|
-
expect(result.value).toBe(100)
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
it('does not apply default when zero is provided', () => {
|
|
282
|
-
const result = schema.safeParse(0)
|
|
283
|
-
expect(result.success).toBe(true)
|
|
284
|
-
if (result.success) {
|
|
285
|
-
expect(result.value).toBe(0)
|
|
286
|
-
}
|
|
287
|
-
})
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
describe('inner schema with boolean default', () => {
|
|
291
|
-
const schema = optional(withDefault(boolean(), true))
|
|
292
|
-
|
|
293
|
-
it('applies default value when undefined is provided', () => {
|
|
294
|
-
const result = schema.safeParse(undefined)
|
|
295
|
-
expect(result.success).toBe(true)
|
|
296
|
-
if (result.success) {
|
|
297
|
-
expect(result.value).toBe(true)
|
|
298
|
-
}
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
it('does not apply default when explicit true is provided', () => {
|
|
302
|
-
const result = schema.safeParse(true)
|
|
303
|
-
expect(result.success).toBe(true)
|
|
304
|
-
if (result.success) {
|
|
305
|
-
expect(result.value).toBe(true)
|
|
306
|
-
}
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
it('does not apply default when explicit false is provided', () => {
|
|
310
|
-
const result = schema.safeParse(false)
|
|
311
|
-
expect(result.success).toBe(true)
|
|
312
|
-
if (result.success) {
|
|
313
|
-
expect(result.value).toBe(false)
|
|
314
|
-
}
|
|
315
|
-
})
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
describe('edge cases', () => {
|
|
319
|
-
const schema = optional(string())
|
|
320
|
-
|
|
321
|
-
it('handles very long strings', () => {
|
|
322
|
-
const longString = 'a'.repeat(10000)
|
|
323
|
-
const result = schema.safeParse(longString)
|
|
324
|
-
expect(result.success).toBe(true)
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
it('handles strings with special characters', () => {
|
|
328
|
-
const result = schema.safeParse('hello\nworld\ttab')
|
|
329
|
-
expect(result.success).toBe(true)
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
it('handles strings with unicode characters', () => {
|
|
333
|
-
const result = schema.safeParse('Hello 世界 🌍')
|
|
334
|
-
expect(result.success).toBe(true)
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
it('handles empty string distinctly from undefined', () => {
|
|
338
|
-
const emptyResult = schema.safeParse('')
|
|
339
|
-
expect(emptyResult.success).toBe(true)
|
|
340
|
-
if (emptyResult.success) {
|
|
341
|
-
expect(emptyResult.value).toBe('')
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const undefinedResult = schema.safeParse(undefined)
|
|
345
|
-
expect(undefinedResult.success).toBe(true)
|
|
346
|
-
if (undefinedResult.success) {
|
|
347
|
-
expect(undefinedResult.value).toBe(undefined)
|
|
348
|
-
}
|
|
349
|
-
})
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
describe('type distinctions', () => {
|
|
353
|
-
it('distinguishes between zero and undefined for integers', () => {
|
|
354
|
-
const schema = optional(integer())
|
|
355
|
-
|
|
356
|
-
const zeroResult = schema.safeParse(0)
|
|
357
|
-
expect(zeroResult.success).toBe(true)
|
|
358
|
-
if (zeroResult.success) {
|
|
359
|
-
expect(zeroResult.value).toBe(0)
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const undefinedResult = schema.safeParse(undefined)
|
|
363
|
-
expect(undefinedResult.success).toBe(true)
|
|
364
|
-
if (undefinedResult.success) {
|
|
365
|
-
expect(undefinedResult.value).toBe(undefined)
|
|
366
|
-
}
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
it('distinguishes between false and undefined for booleans', () => {
|
|
370
|
-
const schema = optional(boolean())
|
|
371
|
-
|
|
372
|
-
const falseResult = schema.safeParse(false)
|
|
373
|
-
expect(falseResult.success).toBe(true)
|
|
374
|
-
if (falseResult.success) {
|
|
375
|
-
expect(falseResult.value).toBe(false)
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const undefinedResult = schema.safeParse(undefined)
|
|
379
|
-
expect(undefinedResult.success).toBe(true)
|
|
380
|
-
if (undefinedResult.success) {
|
|
381
|
-
expect(undefinedResult.value).toBe(undefined)
|
|
382
|
-
}
|
|
383
|
-
})
|
|
384
|
-
|
|
385
|
-
it('distinguishes between empty string and undefined for strings', () => {
|
|
386
|
-
const schema = optional(string())
|
|
387
|
-
|
|
388
|
-
const emptyResult = schema.safeParse('')
|
|
389
|
-
expect(emptyResult.success).toBe(true)
|
|
390
|
-
if (emptyResult.success) {
|
|
391
|
-
expect(emptyResult.value).toBe('')
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const undefinedResult = schema.safeParse(undefined)
|
|
395
|
-
expect(undefinedResult.success).toBe(true)
|
|
396
|
-
if (undefinedResult.success) {
|
|
397
|
-
expect(undefinedResult.value).toBe(undefined)
|
|
398
|
-
}
|
|
399
|
-
})
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
describe('nested optional schemas', () => {
|
|
403
|
-
const schema = optional(optional(string()))
|
|
404
|
-
|
|
405
|
-
it('validates defined values through nested optionals', () => {
|
|
406
|
-
const result = schema.safeParse('hello')
|
|
407
|
-
expect(result.success).toBe(true)
|
|
408
|
-
if (result.success) {
|
|
409
|
-
expect(result.value).toBe('hello')
|
|
410
|
-
}
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
it('validates undefined through nested optionals', () => {
|
|
414
|
-
const result = schema.safeParse(undefined)
|
|
415
|
-
expect(result.success).toBe(true)
|
|
416
|
-
if (result.success) {
|
|
417
|
-
expect(result.value).toBe(undefined)
|
|
418
|
-
}
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
it('rejects invalid types through nested optionals', () => {
|
|
422
|
-
const result = schema.safeParse(123)
|
|
423
|
-
expect(result.success).toBe(false)
|
|
424
|
-
})
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
describe('inner schema format constraints', () => {
|
|
428
|
-
const schema = optional(string({ format: 'uri' }))
|
|
429
|
-
|
|
430
|
-
it('validates values meeting format constraint', () => {
|
|
431
|
-
const result = schema.safeParse('https://example.com')
|
|
432
|
-
expect(result.success).toBe(true)
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
it('validates undefined', () => {
|
|
436
|
-
const result = schema.safeParse(undefined)
|
|
437
|
-
expect(result.success).toBe(true)
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
it('rejects values violating format constraint', () => {
|
|
441
|
-
const result = schema.safeParse('not a uri')
|
|
442
|
-
expect(result.success).toBe(false)
|
|
443
|
-
})
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
describe('integer constraint validation', () => {
|
|
447
|
-
const schema = optional(integer({ minimum: 0, maximum: 100 }))
|
|
448
|
-
|
|
449
|
-
it('validates values within range', () => {
|
|
450
|
-
const result = schema.safeParse(50)
|
|
451
|
-
expect(result.success).toBe(true)
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
it('validates values at minimum boundary', () => {
|
|
455
|
-
const result = schema.safeParse(0)
|
|
456
|
-
expect(result.success).toBe(true)
|
|
457
|
-
})
|
|
458
|
-
|
|
459
|
-
it('validates values at maximum boundary', () => {
|
|
460
|
-
const result = schema.safeParse(100)
|
|
461
|
-
expect(result.success).toBe(true)
|
|
462
|
-
})
|
|
463
|
-
|
|
464
|
-
it('validates undefined', () => {
|
|
465
|
-
const result = schema.safeParse(undefined)
|
|
466
|
-
expect(result.success).toBe(true)
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
it('rejects values below minimum', () => {
|
|
470
|
-
const result = schema.safeParse(-1)
|
|
471
|
-
expect(result.success).toBe(false)
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
it('rejects values above maximum', () => {
|
|
475
|
-
const result = schema.safeParse(101)
|
|
476
|
-
expect(result.success).toBe(false)
|
|
477
|
-
})
|
|
478
|
-
})
|
|
479
|
-
})
|
package/src/schema/optional.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
InferInput,
|
|
3
|
-
InferOutput,
|
|
4
|
-
Schema,
|
|
5
|
-
UnwrapValidator,
|
|
6
|
-
ValidationContext,
|
|
7
|
-
Validator,
|
|
8
|
-
} from '../core.js'
|
|
9
|
-
import { memoizedTransformer } from '../util/memoize.js'
|
|
10
|
-
import { WithDefaultSchema } from './with-default.js'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Schema wrapper that makes a value optional (allows undefined).
|
|
14
|
-
*
|
|
15
|
-
* When the input is `undefined`, validation succeeds without running the
|
|
16
|
-
* inner validator. If the inner validator has a default value (via `withDefault`),
|
|
17
|
-
* that default will be applied in parse mode.
|
|
18
|
-
*
|
|
19
|
-
* @template TValidator - The wrapped validator type
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* const schema = new OptionalSchema(l.string())
|
|
24
|
-
* schema.validate(undefined) // success
|
|
25
|
-
* schema.validate('hello') // success
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export class OptionalSchema<TValidator extends Validator> extends Schema<
|
|
29
|
-
InferInput<TValidator> | undefined,
|
|
30
|
-
UnwrapValidator<TValidator> extends WithDefaultSchema<infer TValidator>
|
|
31
|
-
? InferOutput<TValidator>
|
|
32
|
-
: InferOutput<TValidator> | undefined
|
|
33
|
-
> {
|
|
34
|
-
readonly type = 'optional' as const
|
|
35
|
-
|
|
36
|
-
constructor(readonly validator: TValidator) {
|
|
37
|
-
super()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
41
|
-
// Optimization: No need to apply child schema defaults in validation mode
|
|
42
|
-
if (input === undefined && ctx.options.mode === 'validate') {
|
|
43
|
-
return ctx.success(input)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// @NOTE The inner schema might apply a default value so we need to run it
|
|
47
|
-
// even if input is undefined.
|
|
48
|
-
const result = ctx.validate(input, this.validator)
|
|
49
|
-
|
|
50
|
-
if (result.success) {
|
|
51
|
-
return result
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (input === undefined) {
|
|
55
|
-
return ctx.success(input)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return result
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Creates an optional schema that allows undefined values.
|
|
64
|
-
*
|
|
65
|
-
* Wraps another schema to make it optional. When used in an object schema,
|
|
66
|
-
* properties with optional schemas are not required.
|
|
67
|
-
*
|
|
68
|
-
* @param validator - The validator to make optional
|
|
69
|
-
* @returns A new {@link OptionalSchema} instance
|
|
70
|
-
*
|
|
71
|
-
* @example
|
|
72
|
-
* ```ts
|
|
73
|
-
* // Optional string
|
|
74
|
-
* const optionalBio = l.optional(l.string())
|
|
75
|
-
*
|
|
76
|
-
* // In an object - property is not required
|
|
77
|
-
* const userSchema = l.object({
|
|
78
|
-
* name: l.string(),
|
|
79
|
-
* bio: l.optional(l.string()),
|
|
80
|
-
* })
|
|
81
|
-
* userSchema.parse({ name: 'Alice' }) // Valid, bio is undefined
|
|
82
|
-
*
|
|
83
|
-
* // With default value
|
|
84
|
-
* const countSchema = l.optional(l.withDefault(l.integer(), 0))
|
|
85
|
-
* countSchema.parse(undefined) // Returns 0
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
|
-
export const optional = /*#__PURE__*/ memoizedTransformer(function <
|
|
89
|
-
const TValidator extends Validator,
|
|
90
|
-
>(validator: TValidator) {
|
|
91
|
-
return new OptionalSchema<TValidator>(validator)
|
|
92
|
-
})
|