@atproto/lex-schema 0.1.5 → 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 +14 -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
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { isPlainObject } from '@atproto/lex-data'
|
|
2
|
-
import {
|
|
3
|
-
InferInput,
|
|
4
|
-
InferOutput,
|
|
5
|
-
Schema,
|
|
6
|
-
ValidationContext,
|
|
7
|
-
ValidationResult,
|
|
8
|
-
Validator,
|
|
9
|
-
} from '../core.js'
|
|
10
|
-
import { EnumSchema } from './enum.js'
|
|
11
|
-
import { LiteralSchema } from './literal.js'
|
|
12
|
-
import { ObjectSchema } from './object.js'
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Type representing a single variant in a discriminated union.
|
|
16
|
-
*
|
|
17
|
-
* Must be an ObjectSchema with the discriminator property using either
|
|
18
|
-
* a LiteralSchema or EnumSchema.
|
|
19
|
-
*
|
|
20
|
-
* @template Discriminator - The discriminator property name
|
|
21
|
-
*/
|
|
22
|
-
export type DiscriminatedUnionVariant<Discriminator extends string = string> =
|
|
23
|
-
ObjectSchema<Record<Discriminator, EnumSchema<any> | LiteralSchema<any>>>
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Type representing a non-empty tuple of discriminated union variants.
|
|
27
|
-
*
|
|
28
|
-
* @template TDiscriminator - The discriminator property name
|
|
29
|
-
*/
|
|
30
|
-
export type DiscriminatedUnionVariants<TDiscriminator extends string> =
|
|
31
|
-
readonly [
|
|
32
|
-
DiscriminatedUnionVariant<TDiscriminator>,
|
|
33
|
-
...DiscriminatedUnionVariant<TDiscriminator>[],
|
|
34
|
-
]
|
|
35
|
-
|
|
36
|
-
type DiscriminatedUnionSchemaInput<TVariants extends readonly Validator[]> =
|
|
37
|
-
TVariants extends readonly [
|
|
38
|
-
infer TValidator extends Validator,
|
|
39
|
-
...infer TRest extends readonly Validator[],
|
|
40
|
-
]
|
|
41
|
-
? InferInput<TValidator> | DiscriminatedUnionSchemaInput<TRest>
|
|
42
|
-
: never
|
|
43
|
-
|
|
44
|
-
type DiscriminatedUnionSchemaOutput<TVariants extends readonly Validator[]> =
|
|
45
|
-
TVariants extends readonly [
|
|
46
|
-
infer TValidator extends Validator,
|
|
47
|
-
...infer TRest extends readonly Validator[],
|
|
48
|
-
]
|
|
49
|
-
? InferOutput<TValidator> | DiscriminatedUnionSchemaOutput<TRest>
|
|
50
|
-
: never
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Schema for validating discriminated unions of objects.
|
|
54
|
-
*
|
|
55
|
-
* More efficient than regular union schemas when discriminating on a known
|
|
56
|
-
* property. Looks up the correct variant schema directly based on the
|
|
57
|
-
* discriminator value instead of trying each variant in sequence.
|
|
58
|
-
*
|
|
59
|
-
* @note There is no discriminated union in Lexicon schemas. This is a custom
|
|
60
|
-
* extension to allow optimized validation of union of objects when using the
|
|
61
|
-
* lex library programmatically (i.e. not code generated from a lexicon schema).
|
|
62
|
-
*
|
|
63
|
-
* @template TDiscriminator - The discriminator property name
|
|
64
|
-
* @template TVariants - Tuple type of the variant schemas
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```ts
|
|
68
|
-
* const schema = new DiscriminatedUnionSchema('type', [
|
|
69
|
-
* l.object({ type: l.literal('text'), content: l.string() }),
|
|
70
|
-
* l.object({ type: l.literal('image'), url: l.string() }),
|
|
71
|
-
* ])
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
export class DiscriminatedUnionSchema<
|
|
75
|
-
const TDiscriminator extends string,
|
|
76
|
-
const TVariants extends DiscriminatedUnionVariants<TDiscriminator>,
|
|
77
|
-
> extends Schema<
|
|
78
|
-
DiscriminatedUnionSchemaInput<TVariants>,
|
|
79
|
-
DiscriminatedUnionSchemaOutput<TVariants>
|
|
80
|
-
> {
|
|
81
|
-
readonly type = 'discriminatedUnion' as const
|
|
82
|
-
|
|
83
|
-
readonly variantsMap: Map<unknown, DiscriminatedUnionVariant<TDiscriminator>>
|
|
84
|
-
|
|
85
|
-
constructor(
|
|
86
|
-
readonly discriminator: TDiscriminator,
|
|
87
|
-
readonly variants: TVariants,
|
|
88
|
-
) {
|
|
89
|
-
super()
|
|
90
|
-
|
|
91
|
-
// Although we usually try to avoid initialization work in constructors,
|
|
92
|
-
// here it is necessary to ensure that invalid discriminated throw from the
|
|
93
|
-
// place of construction, rather than later during validation.
|
|
94
|
-
this.variantsMap = buildVariantsMap(discriminator, variants)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
98
|
-
if (!isPlainObject(input)) {
|
|
99
|
-
return ctx.issueUnexpectedType(input, 'object')
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const { discriminator } = this
|
|
103
|
-
|
|
104
|
-
if (!Object.hasOwn(input, discriminator)) {
|
|
105
|
-
return ctx.issueRequiredKey(input, discriminator)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const discriminatorValue = input[discriminator]
|
|
109
|
-
|
|
110
|
-
const variant = this.variantsMap.get(discriminatorValue)
|
|
111
|
-
if (variant) {
|
|
112
|
-
return ctx.validate(input, variant) as ValidationResult<
|
|
113
|
-
DiscriminatedUnionSchemaInput<TVariants>
|
|
114
|
-
>
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return ctx.issueInvalidPropertyValue(input, discriminator, [
|
|
118
|
-
...this.variantsMap.keys(),
|
|
119
|
-
])
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function buildVariantsMap<Discriminator extends string>(
|
|
124
|
-
discriminator: Discriminator,
|
|
125
|
-
variants: DiscriminatedUnionVariants<Discriminator>,
|
|
126
|
-
) {
|
|
127
|
-
const variantsMap = new Map<
|
|
128
|
-
unknown,
|
|
129
|
-
DiscriminatedUnionVariant<Discriminator>
|
|
130
|
-
>()
|
|
131
|
-
|
|
132
|
-
for (const variant of variants) {
|
|
133
|
-
const schema = variant.shape[discriminator]
|
|
134
|
-
if (schema instanceof LiteralSchema) {
|
|
135
|
-
if (variantsMap.has(schema.value)) {
|
|
136
|
-
throw new TypeError(`Overlapping discriminator value: ${schema.value}`)
|
|
137
|
-
}
|
|
138
|
-
variantsMap.set(schema.value, variant)
|
|
139
|
-
} else if (schema instanceof EnumSchema) {
|
|
140
|
-
for (const val of schema.values) {
|
|
141
|
-
if (variantsMap.has(val)) {
|
|
142
|
-
throw new TypeError(`Overlapping discriminator value: ${val}`)
|
|
143
|
-
}
|
|
144
|
-
variantsMap.set(val, variant)
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
// Only enumerable discriminator schemas are supported
|
|
148
|
-
|
|
149
|
-
// Should never happen if types are used correctly
|
|
150
|
-
throw new TypeError(
|
|
151
|
-
`Discriminator schema must be a LiteralSchema or EnumSchema`,
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return variantsMap
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Creates a discriminated union schema for efficient object type switching.
|
|
161
|
-
*
|
|
162
|
-
* Unlike regular `union()`, this schema uses a discriminator property to
|
|
163
|
-
* directly look up the correct variant, providing O(1) validation instead
|
|
164
|
-
* of trying each variant sequentially.
|
|
165
|
-
*
|
|
166
|
-
* @param discriminator - Property name to discriminate on
|
|
167
|
-
* @param variants - Non-empty array of object schemas with the discriminator property
|
|
168
|
-
* @returns A new {@link DiscriminatedUnionSchema} instance
|
|
169
|
-
*
|
|
170
|
-
* @example
|
|
171
|
-
* ```ts
|
|
172
|
-
* // Message types discriminated by 'kind'
|
|
173
|
-
* const messageSchema = l.discriminatedUnion('kind', [
|
|
174
|
-
* l.object({ kind: l.literal('text'), text: l.string() }),
|
|
175
|
-
* l.object({ kind: l.literal('image'), url: l.string(), alt: l.optional(l.string()) }),
|
|
176
|
-
* l.object({ kind: l.literal('video'), url: l.string(), duration: l.integer() }),
|
|
177
|
-
* ])
|
|
178
|
-
*
|
|
179
|
-
* // Using enums for multiple values mapping to same variant
|
|
180
|
-
* const statusSchema = l.discriminatedUnion('status', [
|
|
181
|
-
* l.object({ status: l.enum(['pending', 'processing']), startedAt: l.string() }),
|
|
182
|
-
* l.object({ status: l.literal('completed'), completedAt: l.string() }),
|
|
183
|
-
* l.object({ status: l.literal('failed'), error: l.string() }),
|
|
184
|
-
* ])
|
|
185
|
-
* ```
|
|
186
|
-
*/
|
|
187
|
-
/*@__NO_SIDE_EFFECTS__*/
|
|
188
|
-
export function discriminatedUnion<
|
|
189
|
-
const Discriminator extends string,
|
|
190
|
-
const Options extends DiscriminatedUnionVariants<Discriminator>,
|
|
191
|
-
>(discriminator: Discriminator, variants: Options) {
|
|
192
|
-
return new DiscriminatedUnionSchema<Discriminator, Options>(
|
|
193
|
-
discriminator,
|
|
194
|
-
variants,
|
|
195
|
-
)
|
|
196
|
-
}
|
package/src/schema/enum.test.ts
DELETED
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { enumSchema } from './enum.js'
|
|
3
|
-
import { withDefault } from './with-default.js'
|
|
4
|
-
|
|
5
|
-
describe('EnumSchema', () => {
|
|
6
|
-
describe('with string values', () => {
|
|
7
|
-
const schema = enumSchema(['male', 'female', 'other'])
|
|
8
|
-
|
|
9
|
-
it('validates matching string values', () => {
|
|
10
|
-
const result = schema.safeParse('male')
|
|
11
|
-
expect(result.success).toBe(true)
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('validates all enum values', () => {
|
|
15
|
-
expect(schema.safeParse('male').success).toBe(true)
|
|
16
|
-
expect(schema.safeParse('female').success).toBe(true)
|
|
17
|
-
expect(schema.safeParse('other').success).toBe(true)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('rejects non-matching string values', () => {
|
|
21
|
-
const result = schema.safeParse('unknown')
|
|
22
|
-
expect(result.success).toBe(false)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('rejects null', () => {
|
|
26
|
-
const result = schema.safeParse(null)
|
|
27
|
-
expect(result.success).toBe(false)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
it('rejects undefined', () => {
|
|
31
|
-
const result = schema.safeParse(undefined)
|
|
32
|
-
expect(result.success).toBe(false)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('rejects numbers', () => {
|
|
36
|
-
const result = schema.safeParse(123)
|
|
37
|
-
expect(result.success).toBe(false)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('rejects booleans', () => {
|
|
41
|
-
const result = schema.safeParse(true)
|
|
42
|
-
expect(result.success).toBe(false)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('rejects objects', () => {
|
|
46
|
-
const result = schema.safeParse({ value: 'male' })
|
|
47
|
-
expect(result.success).toBe(false)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('rejects arrays', () => {
|
|
51
|
-
const result = schema.safeParse(['male'])
|
|
52
|
-
expect(result.success).toBe(false)
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('rejects empty string when not in enum', () => {
|
|
56
|
-
const result = schema.safeParse('')
|
|
57
|
-
expect(result.success).toBe(false)
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
describe('with number values', () => {
|
|
62
|
-
const schema = enumSchema([1, 2, 3])
|
|
63
|
-
|
|
64
|
-
it('validates matching number values', () => {
|
|
65
|
-
const result = schema.safeParse(1)
|
|
66
|
-
expect(result.success).toBe(true)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('validates all enum values', () => {
|
|
70
|
-
expect(schema.safeParse(1).success).toBe(true)
|
|
71
|
-
expect(schema.safeParse(2).success).toBe(true)
|
|
72
|
-
expect(schema.safeParse(3).success).toBe(true)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('rejects non-matching number values', () => {
|
|
76
|
-
const result = schema.safeParse(4)
|
|
77
|
-
expect(result.success).toBe(false)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('rejects string numbers', () => {
|
|
81
|
-
const result = schema.safeParse('1')
|
|
82
|
-
expect(result.success).toBe(false)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('rejects zero when not in enum', () => {
|
|
86
|
-
const result = schema.safeParse(0)
|
|
87
|
-
expect(result.success).toBe(false)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('rejects negative numbers when not in enum', () => {
|
|
91
|
-
const result = schema.safeParse(-1)
|
|
92
|
-
expect(result.success).toBe(false)
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
describe('with boolean values', () => {
|
|
97
|
-
const schema = enumSchema([true, false])
|
|
98
|
-
|
|
99
|
-
it('validates true', () => {
|
|
100
|
-
const result = schema.safeParse(true)
|
|
101
|
-
expect(result.success).toBe(true)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('validates false', () => {
|
|
105
|
-
const result = schema.safeParse(false)
|
|
106
|
-
expect(result.success).toBe(true)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('rejects string booleans', () => {
|
|
110
|
-
expect(schema.safeParse('true').success).toBe(false)
|
|
111
|
-
expect(schema.safeParse('false').success).toBe(false)
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('rejects number booleans', () => {
|
|
115
|
-
expect(schema.safeParse(1).success).toBe(false)
|
|
116
|
-
expect(schema.safeParse(0).success).toBe(false)
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
describe('with single boolean value', () => {
|
|
121
|
-
const schema = enumSchema([true])
|
|
122
|
-
|
|
123
|
-
it('validates true', () => {
|
|
124
|
-
const result = schema.safeParse(true)
|
|
125
|
-
expect(result.success).toBe(true)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('rejects false when not in enum', () => {
|
|
129
|
-
const result = schema.safeParse(false)
|
|
130
|
-
expect(result.success).toBe(false)
|
|
131
|
-
})
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
describe('with null value', () => {
|
|
135
|
-
const schema = enumSchema([null, 'value'])
|
|
136
|
-
|
|
137
|
-
it('validates null', () => {
|
|
138
|
-
const result = schema.safeParse(null)
|
|
139
|
-
expect(result.success).toBe(true)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('validates other enum values', () => {
|
|
143
|
-
const result = schema.safeParse('value')
|
|
144
|
-
expect(result.success).toBe(true)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('rejects undefined', () => {
|
|
148
|
-
const result = schema.safeParse(undefined)
|
|
149
|
-
expect(result.success).toBe(false)
|
|
150
|
-
})
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
describe('with mixed type values', () => {
|
|
154
|
-
const schema = enumSchema(['string', 123, true, null])
|
|
155
|
-
|
|
156
|
-
it('validates string value', () => {
|
|
157
|
-
const result = schema.safeParse('string')
|
|
158
|
-
expect(result.success).toBe(true)
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('validates number value', () => {
|
|
162
|
-
const result = schema.safeParse(123)
|
|
163
|
-
expect(result.success).toBe(true)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('validates boolean value', () => {
|
|
167
|
-
const result = schema.safeParse(true)
|
|
168
|
-
expect(result.success).toBe(true)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('validates null value', () => {
|
|
172
|
-
const result = schema.safeParse(null)
|
|
173
|
-
expect(result.success).toBe(true)
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('rejects non-matching values', () => {
|
|
177
|
-
expect(schema.safeParse('other').success).toBe(false)
|
|
178
|
-
expect(schema.safeParse(456).success).toBe(false)
|
|
179
|
-
expect(schema.safeParse(false).success).toBe(false)
|
|
180
|
-
expect(schema.safeParse(undefined).success).toBe(false)
|
|
181
|
-
})
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
describe('with default option', () => {
|
|
185
|
-
const schema = withDefault(enumSchema(['red', 'green', 'blue']), 'red')
|
|
186
|
-
|
|
187
|
-
it('validates matching values', () => {
|
|
188
|
-
const result = schema.safeParse('green')
|
|
189
|
-
expect(result.success).toBe(true)
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
it('uses default when input is undefined', () => {
|
|
193
|
-
const result = schema.safeParse(undefined)
|
|
194
|
-
expect(result.success).toBe(true)
|
|
195
|
-
if (result.success) {
|
|
196
|
-
expect(result.value).toBe('red')
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('uses default when no argument is passed', () => {
|
|
201
|
-
const result = schema.safeParse(undefined)
|
|
202
|
-
expect(result.success).toBe(true)
|
|
203
|
-
if (result.success) {
|
|
204
|
-
expect(result.value).toBe('red')
|
|
205
|
-
}
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
it('does not use default for null', () => {
|
|
209
|
-
const result = schema.safeParse(null)
|
|
210
|
-
expect(result.success).toBe(false)
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('does not use default for invalid values', () => {
|
|
214
|
-
const result = schema.safeParse('yellow')
|
|
215
|
-
expect(result.success).toBe(false)
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
it('does not use default for empty string', () => {
|
|
219
|
-
const result = schema.safeParse('')
|
|
220
|
-
expect(result.success).toBe(false)
|
|
221
|
-
})
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
describe('with default option as number', () => {
|
|
225
|
-
const schema = withDefault(enumSchema([1, 2, 3]), 1)
|
|
226
|
-
|
|
227
|
-
it('uses default when input is undefined', () => {
|
|
228
|
-
const result = schema.safeParse(undefined)
|
|
229
|
-
expect(result.success).toBe(true)
|
|
230
|
-
if (result.success) {
|
|
231
|
-
expect(result.value).toBe(1)
|
|
232
|
-
}
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('does not use default for zero', () => {
|
|
236
|
-
const result = schema.safeParse(0)
|
|
237
|
-
expect(result.success).toBe(false)
|
|
238
|
-
})
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
describe('with default option as boolean', () => {
|
|
242
|
-
const schema = withDefault(enumSchema([true, false]), false)
|
|
243
|
-
|
|
244
|
-
it('uses default when input is undefined', () => {
|
|
245
|
-
const result = schema.safeParse(undefined)
|
|
246
|
-
expect(result.success).toBe(true)
|
|
247
|
-
if (result.success) {
|
|
248
|
-
expect(result.value).toBe(false)
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
it('validates true even when default is false', () => {
|
|
253
|
-
const result = schema.safeParse(true)
|
|
254
|
-
expect(result.success).toBe(true)
|
|
255
|
-
if (result.success) {
|
|
256
|
-
expect(result.value).toBe(true)
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
describe('with default option as null', () => {
|
|
262
|
-
const schema = withDefault(enumSchema([null, 'value']), null)
|
|
263
|
-
|
|
264
|
-
it('uses default when input is undefined', () => {
|
|
265
|
-
const result = schema.safeParse(undefined)
|
|
266
|
-
expect(result.success).toBe(true)
|
|
267
|
-
if (result.success) {
|
|
268
|
-
expect(result.value).toBe(null)
|
|
269
|
-
}
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
it('validates explicit null', () => {
|
|
273
|
-
const result = schema.safeParse(null)
|
|
274
|
-
expect(result.success).toBe(true)
|
|
275
|
-
if (result.success) {
|
|
276
|
-
expect(result.value).toBe(null)
|
|
277
|
-
}
|
|
278
|
-
})
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
describe('with single value', () => {
|
|
282
|
-
const schema = enumSchema(['only'])
|
|
283
|
-
|
|
284
|
-
it('validates the single value', () => {
|
|
285
|
-
const result = schema.safeParse('only')
|
|
286
|
-
expect(result.success).toBe(true)
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
it('rejects any other value', () => {
|
|
290
|
-
expect(schema.safeParse('other').success).toBe(false)
|
|
291
|
-
expect(schema.safeParse('').success).toBe(false)
|
|
292
|
-
expect(schema.safeParse(null).success).toBe(false)
|
|
293
|
-
expect(schema.safeParse(undefined).success).toBe(false)
|
|
294
|
-
})
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
describe('with empty string value', () => {
|
|
298
|
-
const schema = enumSchema(['', 'value'])
|
|
299
|
-
|
|
300
|
-
it('validates empty string', () => {
|
|
301
|
-
const result = schema.safeParse('')
|
|
302
|
-
expect(result.success).toBe(true)
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
it('validates other values', () => {
|
|
306
|
-
const result = schema.safeParse('value')
|
|
307
|
-
expect(result.success).toBe(true)
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
it('rejects non-matching values', () => {
|
|
311
|
-
const result = schema.safeParse('other')
|
|
312
|
-
expect(result.success).toBe(false)
|
|
313
|
-
})
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
describe('with zero value', () => {
|
|
317
|
-
const schema = enumSchema([0, 1, 2])
|
|
318
|
-
|
|
319
|
-
it('validates zero', () => {
|
|
320
|
-
const result = schema.safeParse(0)
|
|
321
|
-
expect(result.success).toBe(true)
|
|
322
|
-
})
|
|
323
|
-
|
|
324
|
-
it('validates other values', () => {
|
|
325
|
-
expect(schema.safeParse(1).success).toBe(true)
|
|
326
|
-
expect(schema.safeParse(2).success).toBe(true)
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
it('rejects false even though zero is in enum', () => {
|
|
330
|
-
const result = schema.safeParse(false)
|
|
331
|
-
expect(result.success).toBe(false)
|
|
332
|
-
})
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
describe('case sensitivity', () => {
|
|
336
|
-
const schema = enumSchema(['Value', 'VALUE', 'value'])
|
|
337
|
-
|
|
338
|
-
it('validates exact case matches', () => {
|
|
339
|
-
expect(schema.safeParse('Value').success).toBe(true)
|
|
340
|
-
expect(schema.safeParse('VALUE').success).toBe(true)
|
|
341
|
-
expect(schema.safeParse('value').success).toBe(true)
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
it('rejects case mismatches', () => {
|
|
345
|
-
expect(schema.safeParse('vaLue').success).toBe(false)
|
|
346
|
-
expect(schema.safeParse('VaLuE').success).toBe(false)
|
|
347
|
-
})
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
describe('with special string values', () => {
|
|
351
|
-
const schema = enumSchema([
|
|
352
|
-
'with space',
|
|
353
|
-
'with\ttab',
|
|
354
|
-
'with\nnewline',
|
|
355
|
-
'123',
|
|
356
|
-
'true',
|
|
357
|
-
'null',
|
|
358
|
-
'undefined',
|
|
359
|
-
])
|
|
360
|
-
|
|
361
|
-
it('validates strings with spaces', () => {
|
|
362
|
-
const result = schema.safeParse('with space')
|
|
363
|
-
expect(result.success).toBe(true)
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
it('validates strings with tabs', () => {
|
|
367
|
-
const result = schema.safeParse('with\ttab')
|
|
368
|
-
expect(result.success).toBe(true)
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
it('validates strings with newlines', () => {
|
|
372
|
-
const result = schema.safeParse('with\nnewline')
|
|
373
|
-
expect(result.success).toBe(true)
|
|
374
|
-
})
|
|
375
|
-
|
|
376
|
-
it('validates number-like strings', () => {
|
|
377
|
-
const result = schema.safeParse('123')
|
|
378
|
-
expect(result.success).toBe(true)
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
it('rejects actual numbers for number-like strings', () => {
|
|
382
|
-
const result = schema.safeParse(123)
|
|
383
|
-
expect(result.success).toBe(false)
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
it('validates keyword strings', () => {
|
|
387
|
-
expect(schema.safeParse('true').success).toBe(true)
|
|
388
|
-
expect(schema.safeParse('null').success).toBe(true)
|
|
389
|
-
expect(schema.safeParse('undefined').success).toBe(true)
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
it('rejects actual boolean/null/undefined for keyword strings', () => {
|
|
393
|
-
expect(schema.safeParse(true).success).toBe(false)
|
|
394
|
-
expect(schema.safeParse(null).success).toBe(false)
|
|
395
|
-
expect(schema.safeParse(undefined).success).toBe(false)
|
|
396
|
-
})
|
|
397
|
-
})
|
|
398
|
-
})
|
package/src/schema/enum.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { Schema, ValidationContext } from '../core.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Schema that accepts one of several specific literal values.
|
|
5
|
-
*
|
|
6
|
-
* Validates that the input matches one of the allowed values using strict
|
|
7
|
-
* equality. Similar to TypeScript union of literals.
|
|
8
|
-
*
|
|
9
|
-
* @template TValue - The union of literal types
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* const schema = new EnumSchema(['pending', 'active', 'completed'])
|
|
14
|
-
* schema.validate('active') // success
|
|
15
|
-
* schema.validate('invalid') // fails
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
export class EnumSchema<
|
|
19
|
-
const TValue extends null | string | number | boolean,
|
|
20
|
-
> extends Schema<TValue> {
|
|
21
|
-
readonly type = 'enum' as const
|
|
22
|
-
|
|
23
|
-
constructor(readonly values: readonly TValue[]) {
|
|
24
|
-
super()
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
28
|
-
if (!(this.values as readonly unknown[]).includes(input)) {
|
|
29
|
-
return ctx.issueInvalidValue(input, this.values)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return ctx.success(input as TValue)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Creates an enum schema that accepts one of the specified values.
|
|
38
|
-
*
|
|
39
|
-
* Similar to TypeScript's union of string literals. Use `l.enum()` for
|
|
40
|
-
* the namespace-friendly alias.
|
|
41
|
-
*
|
|
42
|
-
* @param value - Array of allowed values
|
|
43
|
-
* @returns A new {@link EnumSchema} instance
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* // String enum
|
|
48
|
-
* const statusSchema = l.enum(['pending', 'active', 'completed', 'failed'])
|
|
49
|
-
*
|
|
50
|
-
* // Number enum
|
|
51
|
-
* const prioritySchema = l.enum([1, 2, 3, 4, 5])
|
|
52
|
-
*
|
|
53
|
-
* // Mixed types
|
|
54
|
-
* const mixedSchema = l.enum(['auto', 0, 1, true])
|
|
55
|
-
*
|
|
56
|
-
* // Use in objects
|
|
57
|
-
* const taskSchema = l.object({
|
|
58
|
-
* title: l.string(),
|
|
59
|
-
* status: l.enum(['todo', 'in-progress', 'done']),
|
|
60
|
-
* })
|
|
61
|
-
*
|
|
62
|
-
* // In discriminated unions
|
|
63
|
-
* const resultSchema = l.discriminatedUnion('status', [
|
|
64
|
-
* l.object({ status: l.enum(['pending', 'processing']), progress: l.integer() }),
|
|
65
|
-
* l.object({ status: l.literal('completed'), result: l.unknown() }),
|
|
66
|
-
* ])
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
/*@__NO_SIDE_EFFECTS__*/
|
|
70
|
-
export function enumSchema<const V extends null | string | number | boolean>(
|
|
71
|
-
value: readonly V[],
|
|
72
|
-
) {
|
|
73
|
-
return new EnumSchema<V>(value)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// @NOTE "enum" is a reserved keyword in JS/TS
|
|
77
|
-
export { enumSchema as enum }
|