@atproto/lex-schema 0.0.12 → 0.0.14
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 +53 -0
- package/dist/core/schema.d.ts +27 -36
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +68 -54
- package/dist/core/schema.js.map +1 -1
- package/dist/core/string-format.d.ts +1 -14
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +12 -9
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validation-error.d.ts +5 -5
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +8 -8
- package/dist/core/validation-error.js.map +1 -1
- package/dist/core/validation-issue.js +3 -1
- package/dist/core/validation-issue.js.map +1 -1
- package/dist/core/validator.d.ts +16 -8
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +24 -6
- package/dist/core/validator.js.map +1 -1
- package/dist/helpers.d.ts +10 -11
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js.map +1 -1
- package/dist/schema/array.d.ts +1 -0
- package/dist/schema/array.d.ts.map +1 -1
- package/dist/schema/array.js +2 -1
- package/dist/schema/array.js.map +1 -1
- package/dist/schema/blob.d.ts +4 -2
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +5 -2
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/boolean.d.ts +1 -0
- package/dist/schema/boolean.d.ts.map +1 -1
- package/dist/schema/boolean.js +2 -1
- package/dist/schema/boolean.js.map +1 -1
- package/dist/schema/bytes.d.ts +1 -0
- package/dist/schema/bytes.d.ts.map +1 -1
- package/dist/schema/bytes.js +2 -1
- package/dist/schema/bytes.js.map +1 -1
- package/dist/schema/cid.d.ts +1 -0
- package/dist/schema/cid.d.ts.map +1 -1
- package/dist/schema/cid.js +2 -1
- package/dist/schema/cid.js.map +1 -1
- package/dist/schema/custom.d.ts +1 -0
- package/dist/schema/custom.d.ts.map +1 -1
- package/dist/schema/custom.js +1 -0
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/dict.d.ts +1 -0
- package/dist/schema/dict.d.ts.map +1 -1
- package/dist/schema/dict.js +2 -1
- package/dist/schema/dict.js.map +1 -1
- package/dist/schema/discriminated-union.d.ts +1 -0
- 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.d.ts +1 -0
- package/dist/schema/enum.d.ts.map +1 -1
- package/dist/schema/enum.js +1 -0
- package/dist/schema/enum.js.map +1 -1
- package/dist/schema/integer.d.ts +1 -0
- package/dist/schema/integer.d.ts.map +1 -1
- package/dist/schema/integer.js +2 -1
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/intersection.d.ts +1 -0
- package/dist/schema/intersection.d.ts.map +1 -1
- package/dist/schema/intersection.js +1 -0
- package/dist/schema/intersection.js.map +1 -1
- package/dist/schema/lex-map.d.ts +37 -0
- package/dist/schema/lex-map.d.ts.map +1 -0
- package/dist/schema/lex-map.js +60 -0
- package/dist/schema/lex-map.js.map +1 -0
- package/dist/schema/lex-value.d.ts +35 -0
- package/dist/schema/lex-value.d.ts.map +1 -0
- package/dist/schema/lex-value.js +87 -0
- package/dist/schema/lex-value.js.map +1 -0
- package/dist/schema/literal.d.ts +1 -0
- package/dist/schema/literal.d.ts.map +1 -1
- package/dist/schema/literal.js +1 -0
- package/dist/schema/literal.js.map +1 -1
- package/dist/schema/never.d.ts +1 -0
- package/dist/schema/never.d.ts.map +1 -1
- package/dist/schema/never.js +2 -1
- package/dist/schema/never.js.map +1 -1
- package/dist/schema/null.d.ts +1 -0
- package/dist/schema/null.d.ts.map +1 -1
- package/dist/schema/null.js +2 -1
- package/dist/schema/null.js.map +1 -1
- package/dist/schema/nullable.d.ts +1 -0
- package/dist/schema/nullable.d.ts.map +1 -1
- package/dist/schema/nullable.js +1 -0
- package/dist/schema/nullable.js.map +1 -1
- package/dist/schema/object.d.ts +1 -0
- package/dist/schema/object.d.ts.map +1 -1
- package/dist/schema/object.js +2 -1
- package/dist/schema/object.js.map +1 -1
- package/dist/schema/optional.d.ts +1 -0
- package/dist/schema/optional.d.ts.map +1 -1
- package/dist/schema/optional.js +1 -0
- package/dist/schema/optional.js.map +1 -1
- package/dist/schema/params.d.ts +14 -10
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +87 -24
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js +3 -3
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/record.d.ts +21 -19
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +22 -12
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/ref.d.ts +1 -0
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js +1 -0
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/regexp.d.ts +1 -0
- package/dist/schema/regexp.d.ts.map +1 -1
- package/dist/schema/regexp.js +2 -1
- package/dist/schema/regexp.js.map +1 -1
- package/dist/schema/string.d.ts +22 -6
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +16 -9
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/token.d.ts +1 -0
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +2 -1
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-object.d.ts +20 -16
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +23 -13
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +1 -0
- package/dist/schema/typed-ref.d.ts.map +1 -1
- package/dist/schema/typed-ref.js +1 -0
- package/dist/schema/typed-ref.js.map +1 -1
- package/dist/schema/typed-union.d.ts +1 -0
- package/dist/schema/typed-union.d.ts.map +1 -1
- package/dist/schema/typed-union.js +2 -1
- package/dist/schema/typed-union.js.map +1 -1
- package/dist/schema/union.d.ts +1 -0
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +2 -1
- package/dist/schema/union.js.map +1 -1
- package/dist/schema/unknown.d.ts +1 -0
- package/dist/schema/unknown.d.ts.map +1 -1
- package/dist/schema/unknown.js +1 -0
- package/dist/schema/unknown.js.map +1 -1
- package/dist/schema/with-default.d.ts +1 -0
- package/dist/schema/with-default.d.ts.map +1 -1
- package/dist/schema/with-default.js +1 -0
- package/dist/schema/with-default.js.map +1 -1
- package/dist/schema.d.ts +2 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -1
- package/dist/schema.js.map +1 -1
- package/dist/util/if-any.d.ts +2 -0
- package/dist/util/if-any.d.ts.map +1 -0
- package/dist/util/if-any.js +3 -0
- package/dist/util/if-any.js.map +1 -0
- package/package.json +3 -3
- package/src/core/schema.ts +76 -62
- package/src/core/string-format.ts +14 -17
- package/src/core/validation-error.ts +10 -10
- package/src/core/validation-issue.ts +3 -2
- package/src/core/validator.ts +32 -12
- package/src/helpers.test.ts +1 -1
- package/src/helpers.ts +53 -19
- package/src/schema/array.ts +3 -1
- package/src/schema/blob.ts +4 -1
- package/src/schema/boolean.ts +3 -1
- package/src/schema/bytes.ts +3 -1
- package/src/schema/cid.ts +3 -1
- package/src/schema/custom.ts +2 -0
- package/src/schema/dict.ts +3 -1
- package/src/schema/discriminated-union.ts +3 -1
- package/src/schema/enum.ts +2 -0
- package/src/schema/integer.ts +3 -1
- package/src/schema/intersection.ts +2 -0
- package/src/schema/{unknown-object.test.ts → lex-map.test.ts} +9 -9
- package/src/schema/lex-map.ts +63 -0
- package/src/schema/lex-value.test.ts +81 -0
- package/src/schema/lex-value.ts +86 -0
- package/src/schema/literal.ts +2 -0
- package/src/schema/never.ts +3 -1
- package/src/schema/null.ts +3 -1
- package/src/schema/nullable.ts +2 -0
- package/src/schema/object.ts +3 -1
- package/src/schema/optional.ts +2 -0
- package/src/schema/params.test.ts +98 -43
- package/src/schema/params.ts +136 -39
- package/src/schema/payload.test.ts +2 -2
- package/src/schema/payload.ts +3 -4
- package/src/schema/record.ts +38 -22
- package/src/schema/ref.ts +2 -0
- package/src/schema/regexp.ts +3 -1
- package/src/schema/string.test.ts +99 -2
- package/src/schema/string.ts +58 -15
- package/src/schema/token.ts +3 -1
- package/src/schema/typed-object.test.ts +38 -0
- package/src/schema/typed-object.ts +40 -24
- package/src/schema/typed-ref.ts +2 -0
- package/src/schema/typed-union.ts +3 -1
- package/src/schema/union.ts +4 -2
- package/src/schema/unknown.ts +2 -0
- package/src/schema/with-default.ts +2 -0
- package/src/schema.ts +2 -1
- package/src/util/if-any.ts +3 -0
- package/dist/schema/unknown-object.d.ts +0 -42
- package/dist/schema/unknown-object.d.ts.map +0 -1
- package/dist/schema/unknown-object.js +0 -50
- package/dist/schema/unknown-object.js.map +0 -1
- package/src/schema/unknown-object.ts +0 -53
package/src/schema/boolean.ts
CHANGED
|
@@ -16,12 +16,14 @@ import { memoizedOptions } from '../util/memoize.js'
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
export class BooleanSchema extends Schema<boolean> {
|
|
19
|
+
readonly type = 'boolean' as const
|
|
20
|
+
|
|
19
21
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
20
22
|
if (typeof input === 'boolean') {
|
|
21
23
|
return ctx.success(input)
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
return ctx.
|
|
26
|
+
return ctx.issueUnexpectedType(input, 'boolean')
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
|
package/src/schema/bytes.ts
CHANGED
|
@@ -26,6 +26,8 @@ export type BytesSchemaOptions = {
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
export class BytesSchema extends Schema<Uint8Array> {
|
|
29
|
+
readonly type = 'bytes' as const
|
|
30
|
+
|
|
29
31
|
constructor(readonly options: BytesSchemaOptions = {}) {
|
|
30
32
|
super()
|
|
31
33
|
}
|
|
@@ -35,7 +37,7 @@ export class BytesSchema extends Schema<Uint8Array> {
|
|
|
35
37
|
const bytes =
|
|
36
38
|
ctx.options.mode === 'parse' ? asUint8Array(input) : ifUint8Array(input)
|
|
37
39
|
if (!bytes) {
|
|
38
|
-
return ctx.
|
|
40
|
+
return ctx.issueUnexpectedType(input, 'bytes')
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const { minLength } = this.options
|
package/src/schema/cid.ts
CHANGED
|
@@ -29,13 +29,15 @@ export type CidSchemaOptions = CheckCidOptions
|
|
|
29
29
|
export class CidSchema<
|
|
30
30
|
const TOptions extends CidSchemaOptions = { flavor: undefined },
|
|
31
31
|
> extends Schema<InferCheckedCid<TOptions>> {
|
|
32
|
+
readonly type = 'cid' as const
|
|
33
|
+
|
|
32
34
|
constructor(readonly options?: TOptions) {
|
|
33
35
|
super()
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
37
39
|
if (!isCid(input, this.options)) {
|
|
38
|
-
return ctx.
|
|
40
|
+
return ctx.issueUnexpectedType(input, 'cid')
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
return ctx.success(input)
|
package/src/schema/custom.ts
CHANGED
|
@@ -46,6 +46,8 @@ export type CustomAssertion<TValue> = (
|
|
|
46
46
|
* ```
|
|
47
47
|
*/
|
|
48
48
|
export class CustomSchema<out TValue = unknown> extends Schema<TValue> {
|
|
49
|
+
readonly type = 'custom' as const
|
|
50
|
+
|
|
49
51
|
constructor(
|
|
50
52
|
private readonly assertion: CustomAssertion<TValue>,
|
|
51
53
|
private readonly message: string,
|
package/src/schema/dict.ts
CHANGED
|
@@ -34,6 +34,8 @@ export class DictSchema<
|
|
|
34
34
|
Record<InferInput<TKey>, InferInput<TValue>>,
|
|
35
35
|
Record<InferInput<TKey>, InferOutput<TValue>>
|
|
36
36
|
> {
|
|
37
|
+
readonly type = 'dict' as const
|
|
38
|
+
|
|
37
39
|
constructor(
|
|
38
40
|
readonly keySchema: TKey,
|
|
39
41
|
readonly valueSchema: TValue,
|
|
@@ -47,7 +49,7 @@ export class DictSchema<
|
|
|
47
49
|
options?: { ignoredKeys?: { has(k: string): boolean } },
|
|
48
50
|
) {
|
|
49
51
|
if (!isPlainObject(input)) {
|
|
50
|
-
return ctx.
|
|
52
|
+
return ctx.issueUnexpectedType(input, 'dict')
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
let copy: undefined | Record<string, unknown>
|
|
@@ -78,6 +78,8 @@ export class DiscriminatedUnionSchema<
|
|
|
78
78
|
DiscriminatedUnionSchemaInput<TVariants>,
|
|
79
79
|
DiscriminatedUnionSchemaOutput<TVariants>
|
|
80
80
|
> {
|
|
81
|
+
readonly type = 'discriminatedUnion' as const
|
|
82
|
+
|
|
81
83
|
readonly variantsMap: Map<unknown, DiscriminatedUnionVariant<TDiscriminator>>
|
|
82
84
|
|
|
83
85
|
constructor(
|
|
@@ -94,7 +96,7 @@ export class DiscriminatedUnionSchema<
|
|
|
94
96
|
|
|
95
97
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
96
98
|
if (!isPlainObject(input)) {
|
|
97
|
-
return ctx.
|
|
99
|
+
return ctx.issueUnexpectedType(input, 'object')
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
const { discriminator } = this
|
package/src/schema/enum.ts
CHANGED
|
@@ -18,6 +18,8 @@ import { Schema, ValidationContext } from '../core.js'
|
|
|
18
18
|
export class EnumSchema<
|
|
19
19
|
const TValue extends null | string | number | boolean,
|
|
20
20
|
> extends Schema<TValue> {
|
|
21
|
+
readonly type = 'enum' as const
|
|
22
|
+
|
|
21
23
|
constructor(readonly values: readonly TValue[]) {
|
|
22
24
|
super()
|
|
23
25
|
}
|
package/src/schema/integer.ts
CHANGED
|
@@ -25,13 +25,15 @@ export type IntegerSchemaOptions = {
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export class IntegerSchema extends Schema<number> {
|
|
28
|
+
readonly type = 'integer' as const
|
|
29
|
+
|
|
28
30
|
constructor(readonly options?: IntegerSchemaOptions) {
|
|
29
31
|
super()
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
33
35
|
if (!isInteger(input)) {
|
|
34
|
-
return ctx.
|
|
36
|
+
return ctx.issueUnexpectedType(input, 'integer')
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
if (this.options?.minimum != null && input < this.options.minimum) {
|
|
@@ -56,6 +56,8 @@ export class IntersectionSchema<
|
|
|
56
56
|
Simplify<Intersect<InferInput<Left>, InferInput<Right>>>,
|
|
57
57
|
Simplify<Intersect<InferOutput<Left>, InferOutput<Right>>>
|
|
58
58
|
> {
|
|
59
|
+
readonly type = 'intersection' as const
|
|
60
|
+
|
|
59
61
|
constructor(
|
|
60
62
|
protected readonly left: Left,
|
|
61
63
|
protected readonly right: Right,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
2
|
+
import { lexMap } from './lex-map.js'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
4
|
+
describe(lexMap, () => {
|
|
5
5
|
describe('basic validation', () => {
|
|
6
|
-
const schema =
|
|
6
|
+
const schema = lexMap()
|
|
7
7
|
|
|
8
8
|
it('accepts empty plain objects', () => {
|
|
9
9
|
const result = schema.safeParse({})
|
|
@@ -106,7 +106,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
106
106
|
})
|
|
107
107
|
|
|
108
108
|
describe('rejects non-plain-objects', () => {
|
|
109
|
-
const schema =
|
|
109
|
+
const schema = lexMap()
|
|
110
110
|
|
|
111
111
|
it('rejects strings', () => {
|
|
112
112
|
const result = schema.safeParse('not an object')
|
|
@@ -208,7 +208,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
208
208
|
})
|
|
209
209
|
|
|
210
210
|
describe('rejects invalid value types', () => {
|
|
211
|
-
const schema =
|
|
211
|
+
const schema = lexMap()
|
|
212
212
|
|
|
213
213
|
it('rejects objects with floating point numbers', () => {
|
|
214
214
|
const result = schema.safeParse({ value: 3.14 })
|
|
@@ -288,7 +288,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
288
288
|
})
|
|
289
289
|
|
|
290
290
|
describe('rejects invalid nested values', () => {
|
|
291
|
-
const schema =
|
|
291
|
+
const schema = lexMap()
|
|
292
292
|
|
|
293
293
|
it('rejects deeply nested invalid values', () => {
|
|
294
294
|
const result = schema.safeParse({
|
|
@@ -335,7 +335,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
335
335
|
})
|
|
336
336
|
|
|
337
337
|
describe('edge cases', () => {
|
|
338
|
-
const schema =
|
|
338
|
+
const schema = lexMap()
|
|
339
339
|
|
|
340
340
|
it('accepts objects with numeric string keys', () => {
|
|
341
341
|
const obj = { '0': 'zero', '1': 'one', '2': 'two' }
|
|
@@ -500,7 +500,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
500
500
|
})
|
|
501
501
|
|
|
502
502
|
describe('large objects', () => {
|
|
503
|
-
const schema =
|
|
503
|
+
const schema = lexMap()
|
|
504
504
|
|
|
505
505
|
it('accepts objects with many keys', () => {
|
|
506
506
|
const obj: Record<string, number> = {}
|
|
@@ -538,7 +538,7 @@ describe('UnknownObjectSchema', () => {
|
|
|
538
538
|
})
|
|
539
539
|
|
|
540
540
|
describe('preservation of input', () => {
|
|
541
|
-
const schema =
|
|
541
|
+
const schema = lexMap()
|
|
542
542
|
|
|
543
543
|
it('preserves the original object reference', () => {
|
|
544
544
|
const input = { key: 'value', count: 42 }
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { LexMap, isPlainObject } from '@atproto/lex-data'
|
|
2
|
+
import { Schema, ValidationContext } from '../core.js'
|
|
3
|
+
import { memoizedOptions } from '../util/memoize.js'
|
|
4
|
+
import { lexValue } from './lex-value.js'
|
|
5
|
+
|
|
6
|
+
const propertyValueSchema = /*#__PURE__*/ lexValue()
|
|
7
|
+
|
|
8
|
+
export type { LexMap }
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* AT Protocol lexicon schema definitions with "type": "unknown" are represented
|
|
12
|
+
* as plain objects with string keys and values that are valid AT Protocol data
|
|
13
|
+
* types (string, integer, boolean, null, bytes, cid, array, or object). This
|
|
14
|
+
* type alias corresponds to the expected structure of such "unknown" schema
|
|
15
|
+
* values.
|
|
16
|
+
*/
|
|
17
|
+
export class LexMapSchema extends Schema<LexMap> {
|
|
18
|
+
readonly type = 'lexMap' as const
|
|
19
|
+
|
|
20
|
+
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
21
|
+
if (!isPlainObject(input)) {
|
|
22
|
+
return ctx.issueUnexpectedType(input, 'object')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const key of Object.keys(input)) {
|
|
26
|
+
// @NOTE We use a lexValue() schema here to recursively validate all
|
|
27
|
+
// nested values, which ensures that the error reporting includes the
|
|
28
|
+
// correct path and type information for any invalid nested values. This
|
|
29
|
+
// allows for more informative error descriptions than a simple "isLexMap"
|
|
30
|
+
// check.
|
|
31
|
+
const r = ctx.validateChild(input, key, propertyValueSchema) // recursively validate all properties
|
|
32
|
+
if (!r.success) return r
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return ctx.success(input)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a schema that accepts any plain object with string keys and values
|
|
41
|
+
* that are valid AT Protocol data types (string, integer, boolean, null, bytes,
|
|
42
|
+
* cid, array, or object).
|
|
43
|
+
*
|
|
44
|
+
* @see {@link LexMap} from `@atproto/lex-data` for the type definition of valid AT Protocol data types
|
|
45
|
+
* @returns A new {@link LexMapSchema} instance
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* // Accept any object shape
|
|
50
|
+
* const schema = l.lexMap()
|
|
51
|
+
*
|
|
52
|
+
* schema.validate({ any: 'props' }) // success
|
|
53
|
+
* schema.validate([1, 2, 3]) // fails - only plain objects are accepted
|
|
54
|
+
* schema.validate({ foo: new Date() }) // fails - Date is not a valid LexValue
|
|
55
|
+
* schema.validate({ foo: 1.2 }) // fails - 1.2 is not a valid LexValue (not an integer)
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export const lexMap = /*#__PURE__*/ memoizedOptions(function () {
|
|
59
|
+
return new LexMapSchema()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
/** @deprecated Use {@link lexMap} instead */
|
|
63
|
+
export const unknownObject = lexMap
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import { parseCid } from '@atproto/lex-data'
|
|
3
|
+
import { lexValue } from './lex-value.js'
|
|
4
|
+
|
|
5
|
+
const schema = lexValue()
|
|
6
|
+
|
|
7
|
+
describe(lexValue, () => {
|
|
8
|
+
describe('valid values', () => {
|
|
9
|
+
for (const { note, value } of [
|
|
10
|
+
{ note: 'string', value: 'hello' },
|
|
11
|
+
{ note: 'boolean true', value: true },
|
|
12
|
+
{ note: 'boolean false', value: false },
|
|
13
|
+
{ note: 'null', value: null },
|
|
14
|
+
{ note: 'integer', value: 42 },
|
|
15
|
+
{ note: 'negative integer', value: -1 },
|
|
16
|
+
{ note: 'zero', value: 0 },
|
|
17
|
+
{ note: 'Uint8Array', value: new Uint8Array([1, 2, 3]) },
|
|
18
|
+
{
|
|
19
|
+
note: 'Cid',
|
|
20
|
+
value: parseCid(
|
|
21
|
+
'bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a',
|
|
22
|
+
),
|
|
23
|
+
},
|
|
24
|
+
{ note: 'empty plain object', value: {} },
|
|
25
|
+
{
|
|
26
|
+
note: 'object with Lex values',
|
|
27
|
+
value: {
|
|
28
|
+
a: 123,
|
|
29
|
+
b: 'blah',
|
|
30
|
+
c: true,
|
|
31
|
+
d: null,
|
|
32
|
+
e: new Uint8Array([1, 2, 3]),
|
|
33
|
+
f: { nested: 'value' },
|
|
34
|
+
g: [1, 2, 3],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{ note: 'empty array', value: [] },
|
|
38
|
+
{
|
|
39
|
+
note: 'array with Lex values',
|
|
40
|
+
value: [
|
|
41
|
+
123,
|
|
42
|
+
'blah',
|
|
43
|
+
true,
|
|
44
|
+
null,
|
|
45
|
+
new Uint8Array([1, 2, 3]),
|
|
46
|
+
{ nested: 'value' },
|
|
47
|
+
[1, 2, 3],
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
]) {
|
|
51
|
+
test(note, () => {
|
|
52
|
+
const result = schema.safeParse(value)
|
|
53
|
+
expect(result.success).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('invalid values', () => {
|
|
59
|
+
for (const { note, value } of [
|
|
60
|
+
{ note: 'float', value: 42.5 },
|
|
61
|
+
{ note: 'undefined', value: undefined },
|
|
62
|
+
{ note: 'function', value: () => {} },
|
|
63
|
+
{ note: 'Date object', value: new Date() },
|
|
64
|
+
{ note: 'Map object', value: new Map() },
|
|
65
|
+
{ note: 'Set object', value: new Set() },
|
|
66
|
+
{ note: 'class instance', value: new (class A {})() },
|
|
67
|
+
{ note: 'object with function value', value: { a: 123, b: () => {} } },
|
|
68
|
+
{
|
|
69
|
+
note: 'object with undefined value',
|
|
70
|
+
value: { a: 123, b: undefined },
|
|
71
|
+
},
|
|
72
|
+
{ note: 'array with function', value: [123, 'blah', () => {}] },
|
|
73
|
+
{ note: 'array with undefined', value: [123, 'blah', undefined] },
|
|
74
|
+
]) {
|
|
75
|
+
test(note, () => {
|
|
76
|
+
const result = schema.safeParse(value)
|
|
77
|
+
expect(result.success).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { LexValue, isLexScalar, isPlainObject } from '@atproto/lex-data'
|
|
2
|
+
import { Schema, ValidationContext } from '../core.js'
|
|
3
|
+
import { memoizedOptions } from '../util/memoize.js'
|
|
4
|
+
|
|
5
|
+
export type { LexValue }
|
|
6
|
+
|
|
7
|
+
const EXPECTED_TYPES = Object.freeze([
|
|
8
|
+
// Scalar types
|
|
9
|
+
'null',
|
|
10
|
+
'boolean',
|
|
11
|
+
'integer',
|
|
12
|
+
'string',
|
|
13
|
+
'cid',
|
|
14
|
+
'bytes',
|
|
15
|
+
// Recursive types
|
|
16
|
+
'array',
|
|
17
|
+
'object',
|
|
18
|
+
] as const)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* AT Protocol lexicon values are any valid AT Protocol data types: string,
|
|
22
|
+
* integer, boolean, null, bytes, cid, array, or object.
|
|
23
|
+
*/
|
|
24
|
+
export class LexValueSchema extends Schema<LexValue> {
|
|
25
|
+
readonly type = 'lexValue' as const
|
|
26
|
+
|
|
27
|
+
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
28
|
+
// @NOTE We are *not* using "isLexValue" here to allow for more specific
|
|
29
|
+
// error messages about the path and type of the invalid value. The
|
|
30
|
+
// "isLexValue" check is effectively performed by the recursive validation
|
|
31
|
+
// of child properties below.
|
|
32
|
+
|
|
33
|
+
// @NOTE There are two limitations to the fact that we are not using
|
|
34
|
+
// "isLexValue" here:
|
|
35
|
+
// 1. We cannot detect circular references in objects or arrays, which would
|
|
36
|
+
// cause infinite recursion. However, circular references are not valid
|
|
37
|
+
// AT Protocol data types, so this is not a concern for valid input. This
|
|
38
|
+
// could easily be addressed in the "validateChild" method by keeping
|
|
39
|
+
// track of "parent" objects.
|
|
40
|
+
// 2. We are limited in the recursion depth we can validate due to potential
|
|
41
|
+
// recursion depth limits in JavaScript. However, this is also not a
|
|
42
|
+
// concern for most valid input, as extremely deep nesting is unlikely in
|
|
43
|
+
// typical use cases.
|
|
44
|
+
if (isPlainObject(input)) {
|
|
45
|
+
for (const key of Object.keys(input)) {
|
|
46
|
+
const r = ctx.validateChild(input, key, this) // recursively validate all properties
|
|
47
|
+
if (!r.success) return r
|
|
48
|
+
}
|
|
49
|
+
} else if (Array.isArray(input)) {
|
|
50
|
+
for (let i = 0; i < input.length; i++) {
|
|
51
|
+
const r = ctx.validateChild(input, i, this) // recursively validate all array items
|
|
52
|
+
if (!r.success) return r
|
|
53
|
+
}
|
|
54
|
+
} else if (!isLexScalar(input)) {
|
|
55
|
+
return ctx.issueInvalidType(input, EXPECTED_TYPES)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return ctx.success(input)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a schema that accepts any valid AT Protocol data type: string,
|
|
64
|
+
* integer, boolean, null, bytes, cid, array, or plain object. Arrays and
|
|
65
|
+
* objects are recursively validated to ensure all nested values are also valid
|
|
66
|
+
* AT Protocol data types.
|
|
67
|
+
*
|
|
68
|
+
* @see {@link LexValue} from `@atproto/lex-data` for the type definition of valid AT Protocol data types
|
|
69
|
+
* @returns A new {@link LexValueSchema} instance
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const schema = l.lexValue()
|
|
74
|
+
*
|
|
75
|
+
* schema.validate('hello') // success
|
|
76
|
+
* schema.validate(42) // success
|
|
77
|
+
* schema.validate(null) // success
|
|
78
|
+
* schema.validate([1, 'two', null]) // success
|
|
79
|
+
* schema.validate({ any: 'props' }) // success
|
|
80
|
+
* schema.validate(new Date()) // fails - Date is not a valid LexValue
|
|
81
|
+
* schema.validate({ foo: 1.2 }) // fails - 1.2 is not a valid LexValue (not an integer)
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const lexValue = /*#__PURE__*/ memoizedOptions(function () {
|
|
85
|
+
return new LexValueSchema()
|
|
86
|
+
})
|
package/src/schema/literal.ts
CHANGED
|
@@ -18,6 +18,8 @@ import { Schema, ValidationContext } from '../core.js'
|
|
|
18
18
|
export class LiteralSchema<
|
|
19
19
|
const TValue extends null | string | number | boolean,
|
|
20
20
|
> extends Schema<TValue> {
|
|
21
|
+
readonly type = 'literal' as const
|
|
22
|
+
|
|
21
23
|
constructor(readonly value: TValue) {
|
|
22
24
|
super()
|
|
23
25
|
}
|
package/src/schema/never.ts
CHANGED
|
@@ -14,8 +14,10 @@ import { memoizedOptions } from '../util/memoize.js'
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
export class NeverSchema extends Schema<never> {
|
|
17
|
+
readonly type = 'never' as const
|
|
18
|
+
|
|
17
19
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
18
|
-
return ctx.
|
|
20
|
+
return ctx.issueUnexpectedType(input, 'never')
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
|
package/src/schema/null.ts
CHANGED
|
@@ -15,9 +15,11 @@ import { memoizedOptions } from '../util/memoize.js'
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
export class NullSchema extends Schema<null> {
|
|
18
|
+
readonly type = 'null' as const
|
|
19
|
+
|
|
18
20
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
19
21
|
if (input !== null) {
|
|
20
|
-
return ctx.
|
|
22
|
+
return ctx.issueUnexpectedType(input, 'null')
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
return ctx.success(null)
|
package/src/schema/nullable.ts
CHANGED
|
@@ -26,6 +26,8 @@ export class NullableSchema<const TValidator extends Validator> extends Schema<
|
|
|
26
26
|
InferInput<TValidator> | null,
|
|
27
27
|
InferOutput<TValidator> | null
|
|
28
28
|
> {
|
|
29
|
+
readonly type = 'nullable' as const
|
|
30
|
+
|
|
29
31
|
constructor(readonly validator: TValidator) {
|
|
30
32
|
super()
|
|
31
33
|
}
|
package/src/schema/object.ts
CHANGED
|
@@ -43,6 +43,8 @@ export class ObjectSchema<
|
|
|
43
43
|
[K in keyof TShape]: InferOutput<TShape[K]>
|
|
44
44
|
}>
|
|
45
45
|
> {
|
|
46
|
+
readonly type = 'object' as const
|
|
47
|
+
|
|
46
48
|
constructor(readonly shape: TShape) {
|
|
47
49
|
super()
|
|
48
50
|
}
|
|
@@ -55,7 +57,7 @@ export class ObjectSchema<
|
|
|
55
57
|
|
|
56
58
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
57
59
|
if (!isPlainObject(input)) {
|
|
58
|
-
return ctx.
|
|
60
|
+
return ctx.issueUnexpectedType(input, 'object')
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// Lazily copy value
|
package/src/schema/optional.ts
CHANGED