@atproto/lex-schema 0.0.1 → 0.0.3
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 +68 -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 +8 -4
- 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 +13 -56
- 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 +23 -134
- 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
package/src/schema/typed-ref.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Schema,
|
|
3
|
+
ValidationResult,
|
|
4
|
+
Validator,
|
|
5
|
+
ValidatorContext,
|
|
6
|
+
} from '../validation.js'
|
|
2
7
|
|
|
3
8
|
// Basically a RecordSchema or TypedObjectSchema
|
|
4
9
|
export type TypedRefSchemaValidator<V extends { $type?: string } = any> =
|
|
@@ -12,9 +17,9 @@ export type TypedRefGetter<V extends { $type?: string } = any> =
|
|
|
12
17
|
export type TypedRefSchemaOutput<V extends { $type?: string } = any> =
|
|
13
18
|
V extends { $type?: infer T extends string } ? V & { $type: T } : never
|
|
14
19
|
|
|
15
|
-
export class TypedRefSchema<
|
|
16
|
-
V
|
|
17
|
-
>
|
|
20
|
+
export class TypedRefSchema<V extends { $type?: string } = any> extends Schema<
|
|
21
|
+
TypedRefSchemaOutput<V>
|
|
22
|
+
> {
|
|
18
23
|
#getter: TypedRefGetter<V>
|
|
19
24
|
|
|
20
25
|
constructor(getter: TypedRefGetter<V>) {
|
|
@@ -48,7 +53,7 @@ export class TypedRefSchema<
|
|
|
48
53
|
return this.schema.$type
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
validateInContext(
|
|
52
57
|
input: unknown,
|
|
53
58
|
ctx: ValidatorContext,
|
|
54
59
|
): ValidationResult<TypedRefSchemaOutput<V>> {
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { IntegerSchema } from './integer.js'
|
|
2
|
+
import { ObjectSchema } from './object.js'
|
|
3
|
+
import { StringSchema } from './string.js'
|
|
4
|
+
import { TypedObjectSchema } from './typed-object.js'
|
|
5
|
+
import { TypedRefSchema } from './typed-ref.js'
|
|
6
|
+
import { TypedUnionSchema } from './typed-union.js'
|
|
7
|
+
|
|
8
|
+
describe('TypedUnionSchema', () => {
|
|
9
|
+
const personSchema = new TypedObjectSchema(
|
|
10
|
+
'app.bsky.actor.person',
|
|
11
|
+
new ObjectSchema({
|
|
12
|
+
name: new StringSchema({}),
|
|
13
|
+
age: new IntegerSchema({}),
|
|
14
|
+
}),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const postSchema = new TypedObjectSchema(
|
|
18
|
+
'app.bsky.feed.post',
|
|
19
|
+
new ObjectSchema({
|
|
20
|
+
text: new StringSchema({}),
|
|
21
|
+
createdAt: new StringSchema({}),
|
|
22
|
+
}),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const commentSchema = new TypedObjectSchema(
|
|
26
|
+
'app.bsky.feed.comment',
|
|
27
|
+
new ObjectSchema({
|
|
28
|
+
text: new StringSchema({}),
|
|
29
|
+
parentUri: new StringSchema({}),
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
describe('closed union', () => {
|
|
34
|
+
const schema = new TypedUnionSchema(
|
|
35
|
+
[
|
|
36
|
+
new TypedRefSchema(() => personSchema),
|
|
37
|
+
new TypedRefSchema(() => postSchema),
|
|
38
|
+
],
|
|
39
|
+
true,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
it('validates first type in union', () => {
|
|
43
|
+
const result = schema.safeParse({
|
|
44
|
+
$type: 'app.bsky.actor.person',
|
|
45
|
+
name: 'Alice',
|
|
46
|
+
age: 30,
|
|
47
|
+
})
|
|
48
|
+
expect(result.success).toBe(true)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('validates second type in union', () => {
|
|
52
|
+
const result = schema.safeParse({
|
|
53
|
+
$type: 'app.bsky.feed.post',
|
|
54
|
+
text: 'Hello world',
|
|
55
|
+
createdAt: '2023-01-01T00:00:00Z',
|
|
56
|
+
})
|
|
57
|
+
expect(result.success).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('rejects unknown $type in closed union', () => {
|
|
61
|
+
const result = schema.safeParse({
|
|
62
|
+
$type: 'app.bsky.feed.like',
|
|
63
|
+
subject: 'some-uri',
|
|
64
|
+
})
|
|
65
|
+
expect(result.success).toBe(false)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('rejects object without $type', () => {
|
|
69
|
+
const result = schema.safeParse({
|
|
70
|
+
name: 'Alice',
|
|
71
|
+
age: 30,
|
|
72
|
+
})
|
|
73
|
+
expect(result.success).toBe(false)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('rejects object with invalid property for the $type', () => {
|
|
77
|
+
const result = schema.safeParse({
|
|
78
|
+
$type: 'app.bsky.actor.person',
|
|
79
|
+
name: 'Alice',
|
|
80
|
+
age: 'thirty',
|
|
81
|
+
})
|
|
82
|
+
expect(result.success).toBe(false)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('rejects object missing required properties', () => {
|
|
86
|
+
const result = schema.safeParse({
|
|
87
|
+
$type: 'app.bsky.actor.person',
|
|
88
|
+
name: 'Alice',
|
|
89
|
+
})
|
|
90
|
+
expect(result.success).toBe(false)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('rejects non-object input', () => {
|
|
94
|
+
const result = schema.safeParse('not an object')
|
|
95
|
+
expect(result.success).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('rejects null', () => {
|
|
99
|
+
const result = schema.safeParse(null)
|
|
100
|
+
expect(result.success).toBe(false)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('rejects undefined', () => {
|
|
104
|
+
const result = schema.safeParse(undefined)
|
|
105
|
+
expect(result.success).toBe(false)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('rejects array', () => {
|
|
109
|
+
const result = schema.safeParse([])
|
|
110
|
+
expect(result.success).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('rejects number', () => {
|
|
114
|
+
const result = schema.safeParse(42)
|
|
115
|
+
expect(result.success).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('rejects boolean', () => {
|
|
119
|
+
const result = schema.safeParse(true)
|
|
120
|
+
expect(result.success).toBe(false)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
describe('open union', () => {
|
|
125
|
+
const schema = new TypedUnionSchema(
|
|
126
|
+
[
|
|
127
|
+
new TypedRefSchema(() => personSchema),
|
|
128
|
+
new TypedRefSchema(() => postSchema),
|
|
129
|
+
],
|
|
130
|
+
false,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
it('validates known type', () => {
|
|
134
|
+
const result = schema.safeParse({
|
|
135
|
+
$type: 'app.bsky.actor.person',
|
|
136
|
+
name: 'Alice',
|
|
137
|
+
age: 30,
|
|
138
|
+
})
|
|
139
|
+
expect(result.success).toBe(true)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('validates unknown $type with valid structure', () => {
|
|
143
|
+
const result = schema.safeParse({
|
|
144
|
+
$type: 'app.bsky.feed.like',
|
|
145
|
+
subject: 'some-uri',
|
|
146
|
+
createdAt: '2023-01-01T00:00:00Z',
|
|
147
|
+
})
|
|
148
|
+
expect(result.success).toBe(true)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('accepts any properties for unknown $type', () => {
|
|
152
|
+
const result = schema.safeParse({
|
|
153
|
+
$type: 'unknown.nsid.type',
|
|
154
|
+
anyProperty: 'any value',
|
|
155
|
+
anotherProperty: 123,
|
|
156
|
+
})
|
|
157
|
+
expect(result.success).toBe(true)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('rejects unknown $type with non-string $type', () => {
|
|
161
|
+
const result = schema.safeParse({
|
|
162
|
+
$type: 123,
|
|
163
|
+
someProperty: 'value',
|
|
164
|
+
})
|
|
165
|
+
expect(result.success).toBe(false)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('rejects unknown $type with null $type', () => {
|
|
169
|
+
const result = schema.safeParse({
|
|
170
|
+
$type: null,
|
|
171
|
+
someProperty: 'value',
|
|
172
|
+
})
|
|
173
|
+
expect(result.success).toBe(false)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('rejects object without $type', () => {
|
|
177
|
+
const result = schema.safeParse({
|
|
178
|
+
name: 'Alice',
|
|
179
|
+
age: 30,
|
|
180
|
+
})
|
|
181
|
+
expect(result.success).toBe(false)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('rejects non-object input', () => {
|
|
185
|
+
const result = schema.safeParse('not an object')
|
|
186
|
+
expect(result.success).toBe(false)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('validates known type with extra properties', () => {
|
|
190
|
+
const result = schema.safeParse({
|
|
191
|
+
$type: 'app.bsky.actor.person',
|
|
192
|
+
name: 'Alice',
|
|
193
|
+
age: 30,
|
|
194
|
+
extraProperty: 'extra',
|
|
195
|
+
})
|
|
196
|
+
expect(result.success).toBe(true)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('rejects known type with invalid property types', () => {
|
|
200
|
+
const result = schema.safeParse({
|
|
201
|
+
$type: 'app.bsky.actor.person',
|
|
202
|
+
name: 123,
|
|
203
|
+
age: 30,
|
|
204
|
+
})
|
|
205
|
+
expect(result.success).toBe(false)
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
describe('with three types', () => {
|
|
210
|
+
const schema = new TypedUnionSchema(
|
|
211
|
+
[
|
|
212
|
+
new TypedRefSchema(() => personSchema),
|
|
213
|
+
new TypedRefSchema(() => postSchema),
|
|
214
|
+
new TypedRefSchema(() => commentSchema),
|
|
215
|
+
],
|
|
216
|
+
true,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
it('validates first type', () => {
|
|
220
|
+
const result = schema.safeParse({
|
|
221
|
+
$type: 'app.bsky.actor.person',
|
|
222
|
+
name: 'Alice',
|
|
223
|
+
age: 30,
|
|
224
|
+
})
|
|
225
|
+
expect(result.success).toBe(true)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('validates second type', () => {
|
|
229
|
+
const result = schema.safeParse({
|
|
230
|
+
$type: 'app.bsky.feed.post',
|
|
231
|
+
text: 'Hello',
|
|
232
|
+
createdAt: '2023-01-01T00:00:00Z',
|
|
233
|
+
})
|
|
234
|
+
expect(result.success).toBe(true)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('validates third type', () => {
|
|
238
|
+
const result = schema.safeParse({
|
|
239
|
+
$type: 'app.bsky.feed.comment',
|
|
240
|
+
text: 'Nice post!',
|
|
241
|
+
parentUri: 'at://did:plc:xyz/app.bsky.feed.post/123',
|
|
242
|
+
})
|
|
243
|
+
expect(result.success).toBe(true)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('rejects unknown type', () => {
|
|
247
|
+
const result = schema.safeParse({
|
|
248
|
+
$type: 'app.bsky.feed.like',
|
|
249
|
+
subject: 'some-uri',
|
|
250
|
+
})
|
|
251
|
+
expect(result.success).toBe(false)
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
describe('with single type', () => {
|
|
256
|
+
const schema = new TypedUnionSchema(
|
|
257
|
+
[new TypedRefSchema(() => personSchema)],
|
|
258
|
+
true,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
it('validates the single type', () => {
|
|
262
|
+
const result = schema.safeParse({
|
|
263
|
+
$type: 'app.bsky.actor.person',
|
|
264
|
+
name: 'Alice',
|
|
265
|
+
age: 30,
|
|
266
|
+
})
|
|
267
|
+
expect(result.success).toBe(true)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('rejects different type', () => {
|
|
271
|
+
const result = schema.safeParse({
|
|
272
|
+
$type: 'app.bsky.feed.post',
|
|
273
|
+
text: 'Hello',
|
|
274
|
+
createdAt: '2023-01-01T00:00:00Z',
|
|
275
|
+
})
|
|
276
|
+
expect(result.success).toBe(false)
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
describe('$types getter', () => {
|
|
281
|
+
const schema = new TypedUnionSchema(
|
|
282
|
+
[
|
|
283
|
+
new TypedRefSchema(() => personSchema),
|
|
284
|
+
new TypedRefSchema(() => postSchema),
|
|
285
|
+
],
|
|
286
|
+
true,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
it('returns array of valid $type values', () => {
|
|
290
|
+
const types = schema.$types
|
|
291
|
+
expect(types).toContain('app.bsky.actor.person')
|
|
292
|
+
expect(types).toContain('app.bsky.feed.post')
|
|
293
|
+
expect(types.length).toBe(2)
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
describe('refsMap getter', () => {
|
|
298
|
+
const schema = new TypedUnionSchema(
|
|
299
|
+
[
|
|
300
|
+
new TypedRefSchema(() => personSchema),
|
|
301
|
+
new TypedRefSchema(() => postSchema),
|
|
302
|
+
],
|
|
303
|
+
true,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
it('returns map of $type to ref schema', () => {
|
|
307
|
+
const refsMap = schema.refsMap
|
|
308
|
+
expect(refsMap.size).toBe(2)
|
|
309
|
+
expect(refsMap.has('app.bsky.actor.person')).toBe(true)
|
|
310
|
+
expect(refsMap.has('app.bsky.feed.post')).toBe(true)
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
describe('edge cases', () => {
|
|
315
|
+
const schema = new TypedUnionSchema(
|
|
316
|
+
[
|
|
317
|
+
new TypedRefSchema(() => personSchema),
|
|
318
|
+
new TypedRefSchema(() => postSchema),
|
|
319
|
+
],
|
|
320
|
+
true,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
it('rejects object with $type as empty string in closed union', () => {
|
|
324
|
+
const result = schema.safeParse({
|
|
325
|
+
$type: '',
|
|
326
|
+
name: 'Alice',
|
|
327
|
+
})
|
|
328
|
+
expect(result.success).toBe(false)
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
it('validates object with $type as empty string in open union', () => {
|
|
332
|
+
const openSchema = new TypedUnionSchema(
|
|
333
|
+
[new TypedRefSchema(() => personSchema)],
|
|
334
|
+
false,
|
|
335
|
+
)
|
|
336
|
+
const result = openSchema.safeParse({
|
|
337
|
+
$type: '',
|
|
338
|
+
someProperty: 'value',
|
|
339
|
+
})
|
|
340
|
+
expect(result.success).toBe(true)
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it('rejects plain object with only $type', () => {
|
|
344
|
+
const result = schema.safeParse({
|
|
345
|
+
$type: 'app.bsky.actor.person',
|
|
346
|
+
})
|
|
347
|
+
expect(result.success).toBe(false)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('handles object with $type and undefined properties', () => {
|
|
351
|
+
const result = schema.safeParse({
|
|
352
|
+
$type: 'app.bsky.actor.person',
|
|
353
|
+
name: 'Alice',
|
|
354
|
+
age: 30,
|
|
355
|
+
extra: undefined,
|
|
356
|
+
})
|
|
357
|
+
expect(result.success).toBe(true)
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
describe('closed property', () => {
|
|
362
|
+
it('exposes closed property as true', () => {
|
|
363
|
+
const schema = new TypedUnionSchema(
|
|
364
|
+
[new TypedRefSchema(() => personSchema)],
|
|
365
|
+
true,
|
|
366
|
+
)
|
|
367
|
+
expect(schema.closed).toBe(true)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('exposes closed property as false', () => {
|
|
371
|
+
const schema = new TypedUnionSchema(
|
|
372
|
+
[new TypedRefSchema(() => personSchema)],
|
|
373
|
+
false,
|
|
374
|
+
)
|
|
375
|
+
expect(schema.closed).toBe(false)
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
})
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { isPlainObject } from '@atproto/lex-data'
|
|
2
2
|
import { Restricted, UnknownString } from '../core.js'
|
|
3
|
+
import { lazyProperty } from '../util/lazy-property.js'
|
|
3
4
|
import {
|
|
4
5
|
Infer,
|
|
6
|
+
Schema,
|
|
5
7
|
ValidationResult,
|
|
6
|
-
Validator,
|
|
7
8
|
ValidatorContext,
|
|
8
9
|
} from '../validation.js'
|
|
9
10
|
import { TypedRefSchema, TypedRefSchemaOutput } from './typed-ref.js'
|
|
@@ -43,9 +44,7 @@ export type TypedUnionSchemaOutput<
|
|
|
43
44
|
export class TypedUnionSchema<
|
|
44
45
|
TypedRefs extends readonly TypedRefSchema[] = any,
|
|
45
46
|
Closed extends boolean = any,
|
|
46
|
-
> extends
|
|
47
|
-
readonly lexiconType = 'union' as const
|
|
48
|
-
|
|
47
|
+
> extends Schema<TypedUnionSchemaOutput<TypedRefs, Closed>> {
|
|
49
48
|
constructor(
|
|
50
49
|
protected readonly refs: TypedRefs,
|
|
51
50
|
public readonly closed: Closed,
|
|
@@ -57,26 +56,18 @@ export class TypedUnionSchema<
|
|
|
57
56
|
super()
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
get refsMap(): Map<unknown, TypedRefs[number]> {
|
|
61
60
|
const map = new Map<unknown, TypedRefs[number]>()
|
|
62
61
|
for (const ref of this.refs) map.set(ref.$type, ref)
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
Object.defineProperty(this, 'refsMap', {
|
|
66
|
-
value: map,
|
|
67
|
-
writable: false,
|
|
68
|
-
enumerable: false,
|
|
69
|
-
configurable: true,
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
return map
|
|
63
|
+
return lazyProperty(this, 'refsMap', map)
|
|
73
64
|
}
|
|
74
65
|
|
|
75
66
|
get $types() {
|
|
76
67
|
return Array.from(this.refsMap.keys())
|
|
77
68
|
}
|
|
78
69
|
|
|
79
|
-
|
|
70
|
+
validateInContext(
|
|
80
71
|
input: unknown,
|
|
81
72
|
ctx: ValidatorContext,
|
|
82
73
|
): ValidationResult<TypedUnionSchemaOutput<TypedRefs, Closed>> {
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { BooleanSchema } from './boolean.js'
|
|
2
|
+
import { IntegerSchema } from './integer.js'
|
|
3
|
+
import { ObjectSchema } from './object.js'
|
|
4
|
+
import { StringSchema } from './string.js'
|
|
5
|
+
import { UnionSchema } from './union.js'
|
|
6
|
+
|
|
7
|
+
describe('UnionSchema', () => {
|
|
8
|
+
const stringOrNumber = new UnionSchema([
|
|
9
|
+
new StringSchema({}),
|
|
10
|
+
new IntegerSchema({}),
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
it('validates string input', () => {
|
|
14
|
+
const result = stringOrNumber.safeParse('hello')
|
|
15
|
+
expect(result.success).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('validates number input', () => {
|
|
19
|
+
const result = stringOrNumber.safeParse(42)
|
|
20
|
+
expect(result.success).toBe(true)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('rejects boolean input', () => {
|
|
24
|
+
const result = stringOrNumber.safeParse(true)
|
|
25
|
+
expect(result.success).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('rejects null input', () => {
|
|
29
|
+
const result = stringOrNumber.safeParse(null)
|
|
30
|
+
expect(result.success).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('rejects undefined input', () => {
|
|
34
|
+
const result = stringOrNumber.safeParse(undefined)
|
|
35
|
+
expect(result.success).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('rejects object input when not in union', () => {
|
|
39
|
+
const result = stringOrNumber.safeParse({ key: 'value' })
|
|
40
|
+
expect(result.success).toBe(false)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('rejects array input when not in union', () => {
|
|
44
|
+
const result = stringOrNumber.safeParse([1, 2, 3])
|
|
45
|
+
expect(result.success).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('with object types', () => {
|
|
49
|
+
const schema = new UnionSchema([
|
|
50
|
+
new ObjectSchema({
|
|
51
|
+
type: new StringSchema({}),
|
|
52
|
+
name: new StringSchema({}),
|
|
53
|
+
}),
|
|
54
|
+
new ObjectSchema({
|
|
55
|
+
type: new StringSchema({}),
|
|
56
|
+
age: new IntegerSchema({}),
|
|
57
|
+
}),
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
it('validates first object variant', () => {
|
|
61
|
+
const result = schema.safeParse({
|
|
62
|
+
type: 'person',
|
|
63
|
+
name: 'Alice',
|
|
64
|
+
})
|
|
65
|
+
expect(result.success).toBe(true)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('validates second object variant', () => {
|
|
69
|
+
const result = schema.safeParse({
|
|
70
|
+
type: 'record',
|
|
71
|
+
age: 30,
|
|
72
|
+
})
|
|
73
|
+
expect(result.success).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('rejects object missing required properties', () => {
|
|
77
|
+
const result = schema.safeParse({
|
|
78
|
+
type: 'person',
|
|
79
|
+
})
|
|
80
|
+
expect(result.success).toBe(false)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('rejects object with invalid property types', () => {
|
|
84
|
+
const result = schema.safeParse({
|
|
85
|
+
type: 'record',
|
|
86
|
+
age: 'thirty',
|
|
87
|
+
})
|
|
88
|
+
expect(result.success).toBe(false)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('with three types', () => {
|
|
93
|
+
const schema = new UnionSchema([
|
|
94
|
+
new StringSchema({}),
|
|
95
|
+
new IntegerSchema({}),
|
|
96
|
+
new BooleanSchema({}),
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
it('validates string input', () => {
|
|
100
|
+
const result = schema.safeParse('text')
|
|
101
|
+
expect(result.success).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('validates number input', () => {
|
|
105
|
+
const result = schema.safeParse(123)
|
|
106
|
+
expect(result.success).toBe(true)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('validates boolean input', () => {
|
|
110
|
+
const result = schema.safeParse(false)
|
|
111
|
+
expect(result.success).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('rejects null input', () => {
|
|
115
|
+
const result = schema.safeParse(null)
|
|
116
|
+
expect(result.success).toBe(false)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('rejects array input', () => {
|
|
120
|
+
const result = schema.safeParse([])
|
|
121
|
+
expect(result.success).toBe(false)
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('with constrained types', () => {
|
|
126
|
+
const schema = new UnionSchema([
|
|
127
|
+
new StringSchema({ minLength: 5 }),
|
|
128
|
+
new IntegerSchema({ minimum: 100 }),
|
|
129
|
+
])
|
|
130
|
+
|
|
131
|
+
it('validates string meeting constraint', () => {
|
|
132
|
+
const result = schema.safeParse('hello')
|
|
133
|
+
expect(result.success).toBe(true)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('validates number meeting constraint', () => {
|
|
137
|
+
const result = schema.safeParse(150)
|
|
138
|
+
expect(result.success).toBe(true)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('rejects string not meeting constraint', () => {
|
|
142
|
+
const result = schema.safeParse('hi')
|
|
143
|
+
expect(result.success).toBe(false)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('rejects number not meeting constraint', () => {
|
|
147
|
+
const result = schema.safeParse(50)
|
|
148
|
+
expect(result.success).toBe(false)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('validates first matching type even if later types could match', () => {
|
|
152
|
+
const result = schema.safeParse('valid')
|
|
153
|
+
expect(result.success).toBe(true)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe('edge cases', () => {
|
|
158
|
+
it('validates with single type in union', () => {
|
|
159
|
+
const schema = new UnionSchema([new StringSchema({})])
|
|
160
|
+
const result = schema.safeParse('test')
|
|
161
|
+
expect(result.success).toBe(true)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('rejects when single type in union does not match', () => {
|
|
165
|
+
const schema = new UnionSchema([new StringSchema({})])
|
|
166
|
+
const result = schema.safeParse(123)
|
|
167
|
+
expect(result.success).toBe(false)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('validates empty string', () => {
|
|
171
|
+
const result = stringOrNumber.safeParse('')
|
|
172
|
+
expect(result.success).toBe(true)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('validates zero', () => {
|
|
176
|
+
const result = stringOrNumber.safeParse(0)
|
|
177
|
+
expect(result.success).toBe(true)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('validates negative numbers', () => {
|
|
181
|
+
const result = stringOrNumber.safeParse(-42)
|
|
182
|
+
expect(result.success).toBe(true)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('rejects NaN', () => {
|
|
186
|
+
const result = stringOrNumber.safeParse(NaN)
|
|
187
|
+
expect(result.success).toBe(false)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('rejects Infinity', () => {
|
|
191
|
+
const result = stringOrNumber.safeParse(Infinity)
|
|
192
|
+
expect(result.success).toBe(false)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('rejects -Infinity', () => {
|
|
196
|
+
const result = stringOrNumber.safeParse(-Infinity)
|
|
197
|
+
expect(result.success).toBe(false)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
})
|
package/src/schema/union.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Infer,
|
|
3
|
+
Schema,
|
|
3
4
|
ValidationError,
|
|
4
5
|
ValidationFailure,
|
|
5
6
|
ValidationResult,
|
|
@@ -10,14 +11,14 @@ import {
|
|
|
10
11
|
export type UnionSchemaValidators = readonly [Validator, ...Validator[]]
|
|
11
12
|
export type UnionSchemaOutput<V extends readonly Validator[]> = Infer<V[number]>
|
|
12
13
|
|
|
13
|
-
export class UnionSchema<
|
|
14
|
-
V
|
|
15
|
-
>
|
|
14
|
+
export class UnionSchema<V extends UnionSchemaValidators = any> extends Schema<
|
|
15
|
+
UnionSchemaOutput<V>
|
|
16
|
+
> {
|
|
16
17
|
constructor(protected readonly validators: V) {
|
|
17
18
|
super()
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
validateInContext(
|
|
21
22
|
input: unknown,
|
|
22
23
|
ctx: ValidatorContext,
|
|
23
24
|
): ValidationResult<UnionSchemaOutput<V>> {
|