@atproto/lex-schema 0.0.2 → 0.0.4
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 +75 -0
- package/dist/core/$type.d.ts +6 -3
- package/dist/core/$type.d.ts.map +1 -1
- package/dist/core/$type.js +1 -0
- package/dist/core/$type.js.map +1 -1
- package/dist/core/record-key.d.ts +3 -3
- package/dist/core/record-key.d.ts.map +1 -1
- package/dist/core/record-key.js +12 -6
- package/dist/core/record-key.js.map +1 -1
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +6 -0
- package/dist/core/result.js.map +1 -1
- package/dist/core/string-format.d.ts +30 -27
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +56 -42
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/types.d.ts +9 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/external.d.ts +31 -28
- package/dist/external.d.ts.map +1 -1
- package/dist/external.js +33 -17
- package/dist/external.js.map +1 -1
- package/dist/schema/_parameters.d.ts +2 -2
- package/dist/schema/_parameters.d.ts.map +1 -1
- package/dist/schema/array.d.ts +5 -6
- package/dist/schema/array.d.ts.map +1 -1
- package/dist/schema/array.js +5 -6
- package/dist/schema/array.js.map +1 -1
- package/dist/schema/blob.d.ts +2 -3
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +1 -2
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/boolean.d.ts +4 -5
- package/dist/schema/boolean.d.ts.map +1 -1
- package/dist/schema/boolean.js +2 -3
- package/dist/schema/boolean.js.map +1 -1
- package/dist/schema/bytes.d.ts +3 -4
- package/dist/schema/bytes.d.ts.map +1 -1
- package/dist/schema/bytes.js +2 -3
- package/dist/schema/bytes.js.map +1 -1
- package/dist/schema/cid.d.ts +13 -6
- package/dist/schema/cid.d.ts.map +1 -1
- package/dist/schema/cid.js +2 -4
- package/dist/schema/cid.js.map +1 -1
- package/dist/schema/custom.d.ts +3 -4
- package/dist/schema/custom.d.ts.map +1 -1
- package/dist/schema/custom.js +4 -3
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/dict.d.ts +3 -3
- 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 +15 -24
- package/dist/schema/discriminated-union.d.ts.map +1 -1
- package/dist/schema/discriminated-union.js +40 -64
- package/dist/schema/discriminated-union.js.map +1 -1
- package/dist/schema/enum.d.ts +8 -4
- package/dist/schema/enum.d.ts.map +1 -1
- package/dist/schema/enum.js +5 -3
- package/dist/schema/enum.js.map +1 -1
- package/dist/schema/integer.d.ts +3 -4
- package/dist/schema/integer.d.ts.map +1 -1
- package/dist/schema/integer.js +3 -4
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/intersection.d.ts +22 -14
- package/dist/schema/intersection.d.ts.map +1 -1
- package/dist/schema/intersection.js +12 -22
- package/dist/schema/intersection.js.map +1 -1
- package/dist/schema/literal.d.ts +7 -3
- package/dist/schema/literal.d.ts.map +1 -1
- package/dist/schema/literal.js +5 -3
- package/dist/schema/literal.js.map +1 -1
- package/dist/schema/never.d.ts +2 -2
- package/dist/schema/never.d.ts.map +1 -1
- package/dist/schema/never.js +1 -1
- package/dist/schema/never.js.map +1 -1
- package/dist/schema/null.d.ts +2 -3
- package/dist/schema/null.d.ts.map +1 -1
- package/dist/schema/null.js +1 -2
- package/dist/schema/null.js.map +1 -1
- package/dist/schema/nullable.d.ts +7 -0
- package/dist/schema/nullable.d.ts.map +1 -0
- package/dist/schema/nullable.js +19 -0
- package/dist/schema/nullable.js.map +1 -0
- package/dist/schema/object.d.ts +10 -44
- package/dist/schema/object.d.ts.map +1 -1
- package/dist/schema/object.js +10 -46
- package/dist/schema/object.js.map +1 -1
- package/dist/schema/optional.d.ts +7 -0
- package/dist/schema/optional.d.ts.map +1 -0
- package/dist/schema/optional.js +25 -0
- package/dist/schema/optional.js.map +1 -0
- package/dist/schema/params.d.ts +14 -19
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +10 -24
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts +4 -4
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/permission-set.d.ts +6 -6
- package/dist/schema/permission-set.d.ts.map +1 -1
- package/dist/schema/permission-set.js +1 -2
- package/dist/schema/permission-set.js.map +1 -1
- package/dist/schema/permission.d.ts +0 -1
- package/dist/schema/permission.d.ts.map +1 -1
- package/dist/schema/permission.js +0 -1
- package/dist/schema/permission.js.map +1 -1
- package/dist/schema/procedure.d.ts +8 -9
- package/dist/schema/procedure.d.ts.map +1 -1
- package/dist/schema/procedure.js +0 -1
- package/dist/schema/procedure.js.map +1 -1
- package/dist/schema/query.d.ts +7 -8
- package/dist/schema/query.d.ts.map +1 -1
- package/dist/schema/query.js +0 -1
- package/dist/schema/query.js.map +1 -1
- package/dist/schema/record.d.ts +34 -28
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +1 -2
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/ref.d.ts +2 -3
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js +1 -2
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/refine.d.ts +18 -0
- package/dist/schema/refine.d.ts.map +1 -0
- package/dist/schema/refine.js +33 -0
- package/dist/schema/refine.js.map +1 -0
- package/dist/schema/regexp.d.ts +7 -0
- package/dist/schema/regexp.d.ts.map +1 -0
- package/dist/schema/regexp.js +22 -0
- package/dist/schema/regexp.js.map +1 -0
- package/dist/schema/string.d.ts +4 -8
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +6 -3
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/subscription.d.ts +7 -6
- package/dist/schema/subscription.d.ts.map +1 -1
- package/dist/schema/subscription.js.map +1 -1
- package/dist/schema/token.d.ts +2 -3
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +1 -2
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-object.d.ts +29 -27
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +1 -2
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +2 -2
- 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 +3 -4
- package/dist/schema/typed-union.d.ts.map +1 -1
- package/dist/schema/typed-union.js +3 -10
- package/dist/schema/typed-union.js.map +1 -1
- package/dist/schema/union.d.ts +2 -2
- 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-object.d.ts +2 -3
- package/dist/schema/unknown-object.d.ts.map +1 -1
- package/dist/schema/unknown-object.js +1 -2
- package/dist/schema/unknown-object.js.map +1 -1
- package/dist/schema/unknown.d.ts +2 -2
- package/dist/schema/unknown.d.ts.map +1 -1
- package/dist/schema/unknown.js +1 -1
- package/dist/schema/unknown.js.map +1 -1
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +6 -1
- package/dist/schema.js.map +1 -1
- package/dist/util/array-agg.d.ts.map +1 -1
- package/dist/util/array-agg.js +1 -0
- package/dist/util/array-agg.js.map +1 -1
- package/dist/util/lazy-property.d.ts +2 -0
- package/dist/util/lazy-property.d.ts.map +1 -0
- package/dist/util/lazy-property.js +14 -0
- package/dist/util/lazy-property.js.map +1 -0
- package/dist/validation/schema.d.ts +24 -0
- package/dist/validation/schema.d.ts.map +1 -0
- package/dist/validation/schema.js +57 -0
- package/dist/validation/schema.js.map +1 -0
- package/dist/validation/validation-error.d.ts +3 -3
- package/dist/validation/validation-error.d.ts.map +1 -1
- package/dist/validation/validation-error.js +32 -4
- package/dist/validation/validation-error.js.map +1 -1
- package/dist/validation/validation-issue.d.ts +32 -24
- package/dist/validation/validation-issue.d.ts.map +1 -1
- package/dist/validation/validation-issue.js +136 -92
- package/dist/validation/validation-issue.js.map +1 -1
- package/dist/validation/validator.d.ts +20 -50
- package/dist/validation/validator.d.ts.map +1 -1
- package/dist/validation/validator.js +40 -134
- package/dist/validation/validator.js.map +1 -1
- package/dist/validation.d.ts +1 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +1 -0
- package/dist/validation.js.map +1 -1
- package/package.json +8 -4
- package/src/core/$type.ts +7 -4
- package/src/core/record-key.ts +12 -5
- package/src/core/result.ts +6 -0
- package/src/core/string-format.ts +97 -61
- package/src/core/types.ts +12 -6
- package/src/external.ts +92 -70
- package/src/schema/_parameters.test.ts +416 -0
- package/src/schema/array.test.ts +237 -0
- package/src/schema/array.ts +17 -11
- package/src/schema/blob.test.ts +506 -0
- package/src/schema/blob.ts +3 -5
- package/src/schema/boolean.test.ts +116 -0
- package/src/schema/boolean.ts +5 -7
- package/src/schema/bytes.test.ts +226 -0
- package/src/schema/bytes.ts +4 -6
- package/src/schema/cid.test.ts +155 -0
- package/src/schema/cid.ts +14 -8
- package/src/schema/custom.test.ts +413 -0
- package/src/schema/custom.ts +10 -8
- package/src/schema/dict.test.ts +198 -0
- package/src/schema/dict.ts +6 -8
- package/src/schema/discriminated-union.test.ts +675 -0
- package/src/schema/discriminated-union.ts +68 -95
- package/src/schema/enum.test.ts +396 -0
- package/src/schema/enum.ts +12 -5
- package/src/schema/integer.test.ts +312 -0
- package/src/schema/integer.ts +5 -7
- package/src/schema/intersection.test.ts +32 -0
- package/src/schema/intersection.ts +37 -40
- package/src/schema/literal.test.ts +531 -0
- package/src/schema/literal.ts +12 -5
- package/src/schema/never.test.ts +174 -0
- package/src/schema/never.ts +3 -10
- package/src/schema/null.test.ts +79 -0
- package/src/schema/null.ts +3 -5
- package/src/schema/nullable.test.ts +480 -0
- package/src/schema/nullable.ts +23 -0
- package/src/schema/object.test.ts +47 -115
- package/src/schema/object.ts +19 -123
- package/src/schema/optional.test.ts +485 -0
- package/src/schema/optional.ts +31 -0
- package/src/schema/params.test.ts +582 -0
- package/src/schema/params.ts +37 -55
- package/src/schema/payload.test.ts +345 -0
- package/src/schema/payload.ts +5 -5
- package/src/schema/permission-set.test.ts +679 -0
- package/src/schema/permission-set.ts +6 -8
- package/src/schema/permission.test.ts +536 -0
- package/src/schema/permission.ts +0 -2
- package/src/schema/procedure.test.ts +443 -0
- package/src/schema/procedure.ts +11 -13
- package/src/schema/query.test.ts +408 -0
- package/src/schema/query.ts +9 -11
- package/src/schema/record.test.ts +694 -0
- package/src/schema/record.ts +38 -36
- package/src/schema/ref.test.ts +365 -0
- package/src/schema/ref.ts +8 -5
- package/src/schema/refine.test.ts +578 -0
- package/src/schema/refine.ts +85 -0
- package/src/schema/regexp.test.ts +580 -0
- package/src/schema/regexp.ts +22 -0
- package/src/schema/string.test.ts +612 -0
- package/src/schema/string.ts +11 -17
- package/src/schema/subscription.test.ts +689 -0
- package/src/schema/subscription.ts +13 -8
- package/src/schema/token.test.ts +428 -0
- package/src/schema/token.ts +3 -5
- package/src/schema/typed-object.test.ts +612 -0
- package/src/schema/typed-object.ts +23 -20
- package/src/schema/typed-ref.test.ts +823 -0
- package/src/schema/typed-ref.ts +10 -5
- package/src/schema/typed-union.test.ts +378 -0
- package/src/schema/typed-union.ts +6 -15
- package/src/schema/union.test.ts +200 -0
- package/src/schema/union.ts +5 -4
- package/src/schema/unknown-object.test.ts +592 -0
- package/src/schema/unknown-object.ts +3 -5
- package/src/schema/unknown.test.ts +312 -0
- package/src/schema/unknown.ts +3 -3
- package/src/schema.ts +7 -1
- package/src/util/array-agg.ts +1 -0
- package/src/util/lazy-property.ts +14 -0
- package/src/validation/schema.ts +92 -0
- package/src/validation/validation-error.ts +60 -9
- package/src/validation/validation-issue.ts +141 -144
- package/src/validation/validator.ts +67 -206
- package/src/validation.ts +1 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +7 -0
- package/tsconfig.tests.json +9 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { isPlainObject } from '@atproto/lex-data'
|
|
2
|
-
import { ArrayContaining } from '../core.js'
|
|
3
2
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Infer,
|
|
4
|
+
Schema,
|
|
6
5
|
ValidationResult,
|
|
7
6
|
Validator,
|
|
8
7
|
ValidatorContext,
|
|
@@ -11,28 +10,23 @@ import { EnumSchema } from './enum.js'
|
|
|
11
10
|
import { LiteralSchema } from './literal.js'
|
|
12
11
|
import { ObjectSchema } from './object.js'
|
|
13
12
|
|
|
14
|
-
export type
|
|
15
|
-
ObjectSchema<
|
|
16
|
-
{ [_ in Discriminator]: Validator },
|
|
17
|
-
{ required: ArrayContaining<Discriminator, string> }
|
|
18
|
-
>
|
|
13
|
+
export type DiscriminatedUnionVariant<Discriminator extends string> =
|
|
14
|
+
ObjectSchema<Record<Discriminator, EnumSchema | LiteralSchema>>
|
|
19
15
|
|
|
20
|
-
export type
|
|
16
|
+
export type DiscriminatedUnionVariants<Discriminator extends string> =
|
|
21
17
|
readonly [
|
|
22
|
-
|
|
23
|
-
...
|
|
18
|
+
DiscriminatedUnionVariant<Discriminator>,
|
|
19
|
+
...DiscriminatedUnionVariant<Discriminator>[],
|
|
24
20
|
]
|
|
25
21
|
|
|
26
22
|
export type DiscriminatedUnionSchemaOutput<
|
|
27
|
-
|
|
28
|
-
> =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
? V | DiscriminatedUnionSchemaOutput<Rest>
|
|
35
|
-
: never
|
|
23
|
+
Variants extends readonly Validator[],
|
|
24
|
+
> = Variants extends readonly [
|
|
25
|
+
infer V extends Validator,
|
|
26
|
+
...infer Rest extends readonly Validator[],
|
|
27
|
+
]
|
|
28
|
+
? Infer<V> | DiscriminatedUnionSchemaOutput<Rest>
|
|
29
|
+
: never
|
|
36
30
|
|
|
37
31
|
/**
|
|
38
32
|
* @note There is no discriminated union in Lexicon schemas. This is a custom
|
|
@@ -41,104 +35,83 @@ export type DiscriminatedUnionSchemaOutput<
|
|
|
41
35
|
*/
|
|
42
36
|
export class DiscriminatedUnionSchema<
|
|
43
37
|
const Discriminator extends string = any,
|
|
44
|
-
const
|
|
45
|
-
> extends
|
|
38
|
+
const Variants extends DiscriminatedUnionVariants<Discriminator> = any,
|
|
39
|
+
> extends Schema<DiscriminatedUnionSchemaOutput<Variants>> {
|
|
40
|
+
readonly variantsMap: Map<unknown, DiscriminatedUnionVariant<Discriminator>>
|
|
41
|
+
|
|
46
42
|
constructor(
|
|
47
43
|
readonly discriminator: Discriminator,
|
|
48
|
-
|
|
44
|
+
variants: Variants,
|
|
49
45
|
) {
|
|
50
46
|
super()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* If all variants have a literal or enum for the discriminator property,
|
|
55
|
-
* and there are no overlapping values, returns a map of discriminator values
|
|
56
|
-
* to variants. Otherwise, returns null.
|
|
57
|
-
*/
|
|
58
|
-
protected get variantsMap() {
|
|
59
|
-
const map = new Map<
|
|
60
|
-
unknown,
|
|
61
|
-
DiscriminatedUnionSchemaVariant<Discriminator>
|
|
62
|
-
>()
|
|
63
|
-
for (const variant of this.variants) {
|
|
64
|
-
const schema = variant.validators[this.discriminator]
|
|
65
|
-
if (schema instanceof LiteralSchema) {
|
|
66
|
-
if (map.has(schema.value)) return null // overlapping value
|
|
67
|
-
map.set(schema.value, variant)
|
|
68
|
-
} else if (schema instanceof EnumSchema) {
|
|
69
|
-
for (const val of schema.values) {
|
|
70
|
-
if (map.has(val)) return null // overlapping value
|
|
71
|
-
map.set(val, variant)
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
return null // not a literal or enum
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
47
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
enumerable: false,
|
|
83
|
-
configurable: true,
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
return map
|
|
48
|
+
// Although we usually try to avoid initialization work in constructors,
|
|
49
|
+
// here it is necessary to ensure that invalid discriminated throw from the
|
|
50
|
+
// place of construction, rather than later during validation.
|
|
51
|
+
this.variantsMap = buildVariantsMap(discriminator, variants)
|
|
87
52
|
}
|
|
88
53
|
|
|
89
|
-
|
|
54
|
+
validateInContext(
|
|
90
55
|
input: unknown,
|
|
91
56
|
ctx: ValidatorContext,
|
|
92
|
-
): ValidationResult<DiscriminatedUnionSchemaOutput<
|
|
57
|
+
): ValidationResult<DiscriminatedUnionSchemaOutput<Variants>> {
|
|
93
58
|
if (!isPlainObject(input)) {
|
|
94
59
|
return ctx.issueInvalidType(input, 'object')
|
|
95
60
|
}
|
|
96
61
|
|
|
97
|
-
|
|
98
|
-
|
|
62
|
+
const { discriminator } = this
|
|
63
|
+
|
|
64
|
+
if (!Object.hasOwn(input, discriminator)) {
|
|
65
|
+
return ctx.issueRequiredKey(input, discriminator)
|
|
99
66
|
}
|
|
100
67
|
|
|
101
|
-
|
|
102
|
-
// we can directly select the correct variant to validate against. This also
|
|
103
|
-
// outputs a better error (with a single failure issue) when the discriminator.
|
|
104
|
-
if (this.variantsMap) {
|
|
105
|
-
const variant = this.variantsMap.get(input[this.discriminator])
|
|
106
|
-
if (!variant) {
|
|
107
|
-
return ctx.issueInvalidPropertyValue(input, this.discriminator, [
|
|
108
|
-
...this.variantsMap.keys(),
|
|
109
|
-
])
|
|
110
|
-
}
|
|
68
|
+
const discriminatorValue = input[discriminator]
|
|
111
69
|
|
|
70
|
+
const variant = this.variantsMap.get(discriminatorValue)
|
|
71
|
+
if (variant) {
|
|
112
72
|
return ctx.validate(input, variant) as ValidationResult<
|
|
113
|
-
DiscriminatedUnionSchemaOutput<
|
|
73
|
+
DiscriminatedUnionSchemaOutput<Variants>
|
|
114
74
|
>
|
|
115
75
|
}
|
|
116
76
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const discSchema = variant.validators[this.discriminator]
|
|
123
|
-
const discResult = ctx.validateChild(
|
|
124
|
-
input,
|
|
125
|
-
this.discriminator,
|
|
126
|
-
discSchema,
|
|
127
|
-
)
|
|
77
|
+
return ctx.issueInvalidPropertyValue(input, discriminator, [
|
|
78
|
+
...this.variantsMap.keys(),
|
|
79
|
+
])
|
|
80
|
+
}
|
|
81
|
+
}
|
|
128
82
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
83
|
+
function buildVariantsMap<Discriminator extends string>(
|
|
84
|
+
discriminator: Discriminator,
|
|
85
|
+
variants: DiscriminatedUnionVariants<Discriminator>,
|
|
86
|
+
) {
|
|
87
|
+
const variantsMap = new Map<
|
|
88
|
+
unknown,
|
|
89
|
+
DiscriminatedUnionVariant<Discriminator>
|
|
90
|
+
>()
|
|
91
|
+
|
|
92
|
+
for (const variant of variants) {
|
|
93
|
+
const schema = variant.shape[discriminator]
|
|
94
|
+
if (schema instanceof LiteralSchema) {
|
|
95
|
+
if (variantsMap.has(schema.value)) {
|
|
96
|
+
throw new TypeError(`Overlapping discriminator value: ${schema.value}`)
|
|
132
97
|
}
|
|
98
|
+
variantsMap.set(schema.value, variant)
|
|
99
|
+
} else if (schema instanceof EnumSchema) {
|
|
100
|
+
for (const val of schema.values) {
|
|
101
|
+
if (variantsMap.has(val)) {
|
|
102
|
+
throw new TypeError(`Overlapping discriminator value: ${val}`)
|
|
103
|
+
}
|
|
104
|
+
variantsMap.set(val, variant)
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
// Only enumerable discriminator schemas are supported
|
|
133
108
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
success: false,
|
|
141
|
-
error: ValidationError.fromFailures(failures),
|
|
109
|
+
// Should never happen if types are used correctly
|
|
110
|
+
throw new TypeError(
|
|
111
|
+
`Discriminator schema must be a LiteralSchema or EnumSchema`,
|
|
112
|
+
)
|
|
142
113
|
}
|
|
143
114
|
}
|
|
115
|
+
|
|
116
|
+
return variantsMap
|
|
144
117
|
}
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { EnumSchema } from './enum.js'
|
|
2
|
+
|
|
3
|
+
describe('EnumSchema', () => {
|
|
4
|
+
describe('with string values', () => {
|
|
5
|
+
const schema = new EnumSchema(['male', 'female', 'other'])
|
|
6
|
+
|
|
7
|
+
it('validates matching string values', () => {
|
|
8
|
+
const result = schema.safeParse('male')
|
|
9
|
+
expect(result.success).toBe(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('validates all enum values', () => {
|
|
13
|
+
expect(schema.safeParse('male').success).toBe(true)
|
|
14
|
+
expect(schema.safeParse('female').success).toBe(true)
|
|
15
|
+
expect(schema.safeParse('other').success).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('rejects non-matching string values', () => {
|
|
19
|
+
const result = schema.safeParse('unknown')
|
|
20
|
+
expect(result.success).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('rejects null', () => {
|
|
24
|
+
const result = schema.safeParse(null)
|
|
25
|
+
expect(result.success).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('rejects undefined', () => {
|
|
29
|
+
const result = schema.safeParse(undefined)
|
|
30
|
+
expect(result.success).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('rejects numbers', () => {
|
|
34
|
+
const result = schema.safeParse(123)
|
|
35
|
+
expect(result.success).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('rejects booleans', () => {
|
|
39
|
+
const result = schema.safeParse(true)
|
|
40
|
+
expect(result.success).toBe(false)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('rejects objects', () => {
|
|
44
|
+
const result = schema.safeParse({ value: 'male' })
|
|
45
|
+
expect(result.success).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('rejects arrays', () => {
|
|
49
|
+
const result = schema.safeParse(['male'])
|
|
50
|
+
expect(result.success).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('rejects empty string when not in enum', () => {
|
|
54
|
+
const result = schema.safeParse('')
|
|
55
|
+
expect(result.success).toBe(false)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('with number values', () => {
|
|
60
|
+
const schema = new EnumSchema([1, 2, 3])
|
|
61
|
+
|
|
62
|
+
it('validates matching number values', () => {
|
|
63
|
+
const result = schema.safeParse(1)
|
|
64
|
+
expect(result.success).toBe(true)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('validates all enum values', () => {
|
|
68
|
+
expect(schema.safeParse(1).success).toBe(true)
|
|
69
|
+
expect(schema.safeParse(2).success).toBe(true)
|
|
70
|
+
expect(schema.safeParse(3).success).toBe(true)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('rejects non-matching number values', () => {
|
|
74
|
+
const result = schema.safeParse(4)
|
|
75
|
+
expect(result.success).toBe(false)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('rejects string numbers', () => {
|
|
79
|
+
const result = schema.safeParse('1')
|
|
80
|
+
expect(result.success).toBe(false)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('rejects zero when not in enum', () => {
|
|
84
|
+
const result = schema.safeParse(0)
|
|
85
|
+
expect(result.success).toBe(false)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('rejects negative numbers when not in enum', () => {
|
|
89
|
+
const result = schema.safeParse(-1)
|
|
90
|
+
expect(result.success).toBe(false)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('with boolean values', () => {
|
|
95
|
+
const schema = new EnumSchema([true, false])
|
|
96
|
+
|
|
97
|
+
it('validates true', () => {
|
|
98
|
+
const result = schema.safeParse(true)
|
|
99
|
+
expect(result.success).toBe(true)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('validates false', () => {
|
|
103
|
+
const result = schema.safeParse(false)
|
|
104
|
+
expect(result.success).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('rejects string booleans', () => {
|
|
108
|
+
expect(schema.safeParse('true').success).toBe(false)
|
|
109
|
+
expect(schema.safeParse('false').success).toBe(false)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('rejects number booleans', () => {
|
|
113
|
+
expect(schema.safeParse(1).success).toBe(false)
|
|
114
|
+
expect(schema.safeParse(0).success).toBe(false)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('with single boolean value', () => {
|
|
119
|
+
const schema = new EnumSchema([true])
|
|
120
|
+
|
|
121
|
+
it('validates true', () => {
|
|
122
|
+
const result = schema.safeParse(true)
|
|
123
|
+
expect(result.success).toBe(true)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('rejects false when not in enum', () => {
|
|
127
|
+
const result = schema.safeParse(false)
|
|
128
|
+
expect(result.success).toBe(false)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('with null value', () => {
|
|
133
|
+
const schema = new EnumSchema([null, 'value'])
|
|
134
|
+
|
|
135
|
+
it('validates null', () => {
|
|
136
|
+
const result = schema.safeParse(null)
|
|
137
|
+
expect(result.success).toBe(true)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('validates other enum values', () => {
|
|
141
|
+
const result = schema.safeParse('value')
|
|
142
|
+
expect(result.success).toBe(true)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('rejects undefined', () => {
|
|
146
|
+
const result = schema.safeParse(undefined)
|
|
147
|
+
expect(result.success).toBe(false)
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
describe('with mixed type values', () => {
|
|
152
|
+
const schema = new EnumSchema(['string', 123, true, null])
|
|
153
|
+
|
|
154
|
+
it('validates string value', () => {
|
|
155
|
+
const result = schema.safeParse('string')
|
|
156
|
+
expect(result.success).toBe(true)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('validates number value', () => {
|
|
160
|
+
const result = schema.safeParse(123)
|
|
161
|
+
expect(result.success).toBe(true)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('validates boolean value', () => {
|
|
165
|
+
const result = schema.safeParse(true)
|
|
166
|
+
expect(result.success).toBe(true)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('validates null value', () => {
|
|
170
|
+
const result = schema.safeParse(null)
|
|
171
|
+
expect(result.success).toBe(true)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('rejects non-matching values', () => {
|
|
175
|
+
expect(schema.safeParse('other').success).toBe(false)
|
|
176
|
+
expect(schema.safeParse(456).success).toBe(false)
|
|
177
|
+
expect(schema.safeParse(false).success).toBe(false)
|
|
178
|
+
expect(schema.safeParse(undefined).success).toBe(false)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
describe('with default option', () => {
|
|
183
|
+
const schema = new EnumSchema(['red', 'green', 'blue'], { default: 'red' })
|
|
184
|
+
|
|
185
|
+
it('validates matching values', () => {
|
|
186
|
+
const result = schema.safeParse('green')
|
|
187
|
+
expect(result.success).toBe(true)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('uses default when input is undefined', () => {
|
|
191
|
+
const result = schema.safeParse(undefined)
|
|
192
|
+
expect(result.success).toBe(true)
|
|
193
|
+
if (result.success) {
|
|
194
|
+
expect(result.value).toBe('red')
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('uses default when no argument is passed', () => {
|
|
199
|
+
const result = schema.safeParse(undefined)
|
|
200
|
+
expect(result.success).toBe(true)
|
|
201
|
+
if (result.success) {
|
|
202
|
+
expect(result.value).toBe('red')
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('does not use default for null', () => {
|
|
207
|
+
const result = schema.safeParse(null)
|
|
208
|
+
expect(result.success).toBe(false)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('does not use default for invalid values', () => {
|
|
212
|
+
const result = schema.safeParse('yellow')
|
|
213
|
+
expect(result.success).toBe(false)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('does not use default for empty string', () => {
|
|
217
|
+
const result = schema.safeParse('')
|
|
218
|
+
expect(result.success).toBe(false)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
describe('with default option as number', () => {
|
|
223
|
+
const schema = new EnumSchema([1, 2, 3], { default: 1 })
|
|
224
|
+
|
|
225
|
+
it('uses default when input is undefined', () => {
|
|
226
|
+
const result = schema.safeParse(undefined)
|
|
227
|
+
expect(result.success).toBe(true)
|
|
228
|
+
if (result.success) {
|
|
229
|
+
expect(result.value).toBe(1)
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('does not use default for zero', () => {
|
|
234
|
+
const result = schema.safeParse(0)
|
|
235
|
+
expect(result.success).toBe(false)
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
describe('with default option as boolean', () => {
|
|
240
|
+
const schema = new EnumSchema([true, false], { default: false })
|
|
241
|
+
|
|
242
|
+
it('uses default when input is undefined', () => {
|
|
243
|
+
const result = schema.safeParse(undefined)
|
|
244
|
+
expect(result.success).toBe(true)
|
|
245
|
+
if (result.success) {
|
|
246
|
+
expect(result.value).toBe(false)
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('validates true even when default is false', () => {
|
|
251
|
+
const result = schema.safeParse(true)
|
|
252
|
+
expect(result.success).toBe(true)
|
|
253
|
+
if (result.success) {
|
|
254
|
+
expect(result.value).toBe(true)
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
describe('with default option as null', () => {
|
|
260
|
+
const schema = new EnumSchema([null, 'value'], { default: null })
|
|
261
|
+
|
|
262
|
+
it('uses default when input is undefined', () => {
|
|
263
|
+
const result = schema.safeParse(undefined)
|
|
264
|
+
expect(result.success).toBe(true)
|
|
265
|
+
if (result.success) {
|
|
266
|
+
expect(result.value).toBe(null)
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('validates explicit null', () => {
|
|
271
|
+
const result = schema.safeParse(null)
|
|
272
|
+
expect(result.success).toBe(true)
|
|
273
|
+
if (result.success) {
|
|
274
|
+
expect(result.value).toBe(null)
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
describe('with single value', () => {
|
|
280
|
+
const schema = new EnumSchema(['only'])
|
|
281
|
+
|
|
282
|
+
it('validates the single value', () => {
|
|
283
|
+
const result = schema.safeParse('only')
|
|
284
|
+
expect(result.success).toBe(true)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('rejects any other value', () => {
|
|
288
|
+
expect(schema.safeParse('other').success).toBe(false)
|
|
289
|
+
expect(schema.safeParse('').success).toBe(false)
|
|
290
|
+
expect(schema.safeParse(null).success).toBe(false)
|
|
291
|
+
expect(schema.safeParse(undefined).success).toBe(false)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
describe('with empty string value', () => {
|
|
296
|
+
const schema = new EnumSchema(['', 'value'])
|
|
297
|
+
|
|
298
|
+
it('validates empty string', () => {
|
|
299
|
+
const result = schema.safeParse('')
|
|
300
|
+
expect(result.success).toBe(true)
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('validates other values', () => {
|
|
304
|
+
const result = schema.safeParse('value')
|
|
305
|
+
expect(result.success).toBe(true)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('rejects non-matching values', () => {
|
|
309
|
+
const result = schema.safeParse('other')
|
|
310
|
+
expect(result.success).toBe(false)
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
describe('with zero value', () => {
|
|
315
|
+
const schema = new EnumSchema([0, 1, 2])
|
|
316
|
+
|
|
317
|
+
it('validates zero', () => {
|
|
318
|
+
const result = schema.safeParse(0)
|
|
319
|
+
expect(result.success).toBe(true)
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('validates other values', () => {
|
|
323
|
+
expect(schema.safeParse(1).success).toBe(true)
|
|
324
|
+
expect(schema.safeParse(2).success).toBe(true)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('rejects false even though zero is in enum', () => {
|
|
328
|
+
const result = schema.safeParse(false)
|
|
329
|
+
expect(result.success).toBe(false)
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
describe('case sensitivity', () => {
|
|
334
|
+
const schema = new EnumSchema(['Value', 'VALUE', 'value'])
|
|
335
|
+
|
|
336
|
+
it('validates exact case matches', () => {
|
|
337
|
+
expect(schema.safeParse('Value').success).toBe(true)
|
|
338
|
+
expect(schema.safeParse('VALUE').success).toBe(true)
|
|
339
|
+
expect(schema.safeParse('value').success).toBe(true)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('rejects case mismatches', () => {
|
|
343
|
+
expect(schema.safeParse('vaLue').success).toBe(false)
|
|
344
|
+
expect(schema.safeParse('VaLuE').success).toBe(false)
|
|
345
|
+
})
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
describe('with special string values', () => {
|
|
349
|
+
const schema = new EnumSchema([
|
|
350
|
+
'with space',
|
|
351
|
+
'with\ttab',
|
|
352
|
+
'with\nnewline',
|
|
353
|
+
'123',
|
|
354
|
+
'true',
|
|
355
|
+
'null',
|
|
356
|
+
'undefined',
|
|
357
|
+
])
|
|
358
|
+
|
|
359
|
+
it('validates strings with spaces', () => {
|
|
360
|
+
const result = schema.safeParse('with space')
|
|
361
|
+
expect(result.success).toBe(true)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('validates strings with tabs', () => {
|
|
365
|
+
const result = schema.safeParse('with\ttab')
|
|
366
|
+
expect(result.success).toBe(true)
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
it('validates strings with newlines', () => {
|
|
370
|
+
const result = schema.safeParse('with\nnewline')
|
|
371
|
+
expect(result.success).toBe(true)
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('validates number-like strings', () => {
|
|
375
|
+
const result = schema.safeParse('123')
|
|
376
|
+
expect(result.success).toBe(true)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('rejects actual numbers for number-like strings', () => {
|
|
380
|
+
const result = schema.safeParse(123)
|
|
381
|
+
expect(result.success).toBe(false)
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('validates keyword strings', () => {
|
|
385
|
+
expect(schema.safeParse('true').success).toBe(true)
|
|
386
|
+
expect(schema.safeParse('null').success).toBe(true)
|
|
387
|
+
expect(schema.safeParse('undefined').success).toBe(true)
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('rejects actual boolean/null/undefined for keyword strings', () => {
|
|
391
|
+
expect(schema.safeParse(true).success).toBe(false)
|
|
392
|
+
expect(schema.safeParse(null).success).toBe(false)
|
|
393
|
+
expect(schema.safeParse(undefined).success).toBe(false)
|
|
394
|
+
})
|
|
395
|
+
})
|
|
396
|
+
})
|
package/src/schema/enum.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Schema, ValidationResult, ValidatorContext } from '../validation.js'
|
|
2
|
+
|
|
3
|
+
export type EnumSchemaOptions<T extends null | string | number | boolean> = {
|
|
4
|
+
default?: T
|
|
5
|
+
}
|
|
2
6
|
|
|
3
7
|
export class EnumSchema<
|
|
4
8
|
Output extends null | string | number | boolean = any,
|
|
5
|
-
> extends
|
|
6
|
-
constructor(
|
|
9
|
+
> extends Schema<Output> {
|
|
10
|
+
constructor(
|
|
11
|
+
readonly values: readonly Output[],
|
|
12
|
+
readonly options?: EnumSchemaOptions<Output>,
|
|
13
|
+
) {
|
|
7
14
|
super()
|
|
8
15
|
}
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
input: unknown,
|
|
17
|
+
validateInContext(
|
|
18
|
+
input: unknown = this.options?.default,
|
|
12
19
|
ctx: ValidatorContext,
|
|
13
20
|
): ValidationResult<Output> {
|
|
14
21
|
if (!(this.values as readonly unknown[]).includes(input)) {
|