@atproto/lex-schema 0.0.13 → 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 +18 -0
- package/dist/core/schema.d.ts +23 -32
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +67 -53
- 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/validator.d.ts +6 -6
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +3 -3
- package/dist/core/validator.js.map +1 -1
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +4 -1
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/record.d.ts +12 -6
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +21 -12
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/typed-object.d.ts +12 -4
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +21 -12
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +1 -1
- package/dist/schema/union.js.map +1 -1
- package/package.json +3 -3
- package/src/core/schema.ts +67 -59
- package/src/core/string-format.ts +14 -17
- package/src/core/validation-error.ts +10 -10
- package/src/core/validator.ts +9 -9
- package/src/schema/params.test.ts +16 -0
- package/src/schema/params.ts +5 -2
- package/src/schema/record.ts +27 -22
- package/src/schema/typed-object.test.ts +38 -0
- package/src/schema/typed-object.ts +29 -24
- package/src/schema/union.ts +2 -2
package/src/core/validator.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PropertyKey } from './property-key.js'
|
|
2
2
|
import { ResultFailure, ResultSuccess, failure, success } from './result.js'
|
|
3
|
-
import {
|
|
3
|
+
import { LexValidationError } from './validation-error.js'
|
|
4
4
|
import {
|
|
5
5
|
Issue,
|
|
6
6
|
IssueInvalidFormat,
|
|
@@ -20,16 +20,16 @@ import {
|
|
|
20
20
|
export type ValidationSuccess<Value = unknown> = ResultSuccess<Value>
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Represents a failed validation result containing a {@link
|
|
23
|
+
* Represents a failed validation result containing a {@link LexValidationError}.
|
|
24
24
|
*/
|
|
25
|
-
export type ValidationFailure = ResultFailure<
|
|
25
|
+
export type ValidationFailure = ResultFailure<LexValidationError>
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Discriminated union representing the outcome of a validation operation.
|
|
29
29
|
*
|
|
30
30
|
* Check the `success` property to determine if validation passed or failed:
|
|
31
31
|
* - If `success` is `true`, the `value` property contains the validated data
|
|
32
|
-
* - If `success` is `false`, the `reason` property contains the {@link
|
|
32
|
+
* - If `success` is `false`, the `reason` property contains the {@link LexValidationError}
|
|
33
33
|
*
|
|
34
34
|
* @typeParam Value - The type of the validated value on success
|
|
35
35
|
*
|
|
@@ -39,7 +39,7 @@ export type ValidationFailure = ResultFailure<ValidationError>
|
|
|
39
39
|
* if (result.success) {
|
|
40
40
|
* // result.value is string
|
|
41
41
|
* } else {
|
|
42
|
-
* // result.reason is
|
|
42
|
+
* // result.reason is LexValidationError
|
|
43
43
|
* }
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
@@ -326,7 +326,7 @@ export class ValidationContext {
|
|
|
326
326
|
if (this.issues.length > 0) {
|
|
327
327
|
// Validator returned a success but issues were added via the context.
|
|
328
328
|
// This means the overall validation failed.
|
|
329
|
-
return failure(new
|
|
329
|
+
return failure(new LexValidationError(Array.from(this.issues)))
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
if (this.options.mode !== 'parse' && !Object.is(result.value, input)) {
|
|
@@ -341,7 +341,7 @@ export class ValidationContext {
|
|
|
341
341
|
// another.
|
|
342
342
|
|
|
343
343
|
// This if block comes before the next one because 'this.issues' will
|
|
344
|
-
// end-up being appended to the returned
|
|
344
|
+
// end-up being appended to the returned LexValidationError (see the
|
|
345
345
|
// "failure" method below), resulting in a more complete error report.
|
|
346
346
|
return this.issueInvalidValue(input, [result.value])
|
|
347
347
|
}
|
|
@@ -425,7 +425,7 @@ export class ValidationContext {
|
|
|
425
425
|
* @param reason - The validation error
|
|
426
426
|
* @returns A failed validation result
|
|
427
427
|
*/
|
|
428
|
-
failure(reason:
|
|
428
|
+
failure(reason: LexValidationError): ValidationFailure {
|
|
429
429
|
return failure(reason)
|
|
430
430
|
}
|
|
431
431
|
|
|
@@ -438,7 +438,7 @@ export class ValidationContext {
|
|
|
438
438
|
* @returns A failed validation result
|
|
439
439
|
*/
|
|
440
440
|
issue(issue: Issue) {
|
|
441
|
-
return this.failure(new
|
|
441
|
+
return this.failure(new LexValidationError([...this.issues, issue]))
|
|
442
442
|
}
|
|
443
443
|
|
|
444
444
|
/**
|
|
@@ -433,6 +433,22 @@ describe('ParamsSchema', () => {
|
|
|
433
433
|
),
|
|
434
434
|
).toThrow('Expected boolean value type at $.foo.bar.bools (got string)')
|
|
435
435
|
})
|
|
436
|
+
|
|
437
|
+
it('ignores empty string values', () => {
|
|
438
|
+
const result = schema.fromURLSearchParams([
|
|
439
|
+
['name', 'Alice'],
|
|
440
|
+
['extra', ''],
|
|
441
|
+
])
|
|
442
|
+
expect(result).toEqual({ name: 'Alice' })
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('ignores empty string values for known parameters', () => {
|
|
446
|
+
const result = schema.fromURLSearchParams([
|
|
447
|
+
['name', 'Alice'],
|
|
448
|
+
['age', ''],
|
|
449
|
+
])
|
|
450
|
+
expect(result).toEqual({ name: 'Alice' })
|
|
451
|
+
})
|
|
436
452
|
})
|
|
437
453
|
|
|
438
454
|
describe('toURLSearchParams', () => {
|
package/src/schema/params.ts
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
Issue,
|
|
7
7
|
IssueInvalidType,
|
|
8
8
|
IssueInvalidValue,
|
|
9
|
+
LexValidationError,
|
|
9
10
|
ParseOptions,
|
|
10
11
|
Schema,
|
|
11
12
|
ValidationContext,
|
|
12
|
-
ValidationError,
|
|
13
13
|
Validator,
|
|
14
14
|
WithOptionalProperties,
|
|
15
15
|
} from '../core.js'
|
|
@@ -209,6 +209,9 @@ export class ParamsSchema<
|
|
|
209
209
|
iterable instanceof URLSearchParams ? iterable.entries() : iterable
|
|
210
210
|
|
|
211
211
|
for (const [name, value] of entries) {
|
|
212
|
+
// Ignore empty strings
|
|
213
|
+
if (!value) continue
|
|
214
|
+
|
|
212
215
|
const validator = this.shapeValidators.get(name)
|
|
213
216
|
const innerValidator = validator ? unwrapSchema(validator) : undefined
|
|
214
217
|
const expectsArray = innerValidator instanceof ArraySchema
|
|
@@ -302,7 +305,7 @@ function coerceParam(
|
|
|
302
305
|
// the index of the param in case of array params (e.g. "tags[1]"), which
|
|
303
306
|
// could be helpful for debugging. The cost overhead is not worth it though
|
|
304
307
|
// (IMO).
|
|
305
|
-
throw new
|
|
308
|
+
throw new LexValidationError([issue])
|
|
306
309
|
}
|
|
307
310
|
|
|
308
311
|
function paramPath(key: string, options?: ParseOptions) {
|
package/src/schema/record.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ValidationContext,
|
|
12
12
|
Validator,
|
|
13
13
|
} from '../core.js'
|
|
14
|
+
import { lazyProperty } from '../util/lazy-property.js'
|
|
14
15
|
import { literal } from './literal.js'
|
|
15
16
|
import { string } from './string.js'
|
|
16
17
|
|
|
@@ -70,10 +71,18 @@ export class RecordSchema<
|
|
|
70
71
|
this.keySchema = recordKey(key)
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
75
|
+
const result = ctx.validate(input, this.schema)
|
|
76
|
+
|
|
77
|
+
if (!result.success) {
|
|
78
|
+
return result
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (result.value.$type !== this.$type) {
|
|
82
|
+
return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return result
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
build(
|
|
@@ -82,30 +91,26 @@ export class RecordSchema<
|
|
|
82
91
|
return this.parse($typed(input, this.$type))
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
|
|
94
|
+
isTypeOf<TValue extends { $type?: unknown }>(
|
|
86
95
|
value: TValue,
|
|
87
96
|
): value is TypedRecord<TType, TValue> {
|
|
88
|
-
return this
|
|
97
|
+
return value.$type === this.$type
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Bound alias for {@link build} for compatibility with generated utilities.
|
|
102
|
+
* @see {@link build}
|
|
103
|
+
*/
|
|
104
|
+
get $build(): typeof this.build {
|
|
105
|
+
return lazyProperty(this, '$build', this.build.bind(this))
|
|
95
106
|
}
|
|
96
107
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (result.value.$type !== this.$type) {
|
|
105
|
-
return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return result
|
|
108
|
+
/**
|
|
109
|
+
* Bound alias for {@link isTypeOf} for compatibility with generated utilities.
|
|
110
|
+
* @see {@link isTypeOf}
|
|
111
|
+
*/
|
|
112
|
+
get $isTypeOf(): typeof this.isTypeOf {
|
|
113
|
+
return lazyProperty(this, '$isTypeOf', this.isTypeOf.bind(this))
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -332,6 +332,44 @@ describe('TypedObjectSchema', () => {
|
|
|
332
332
|
})
|
|
333
333
|
})
|
|
334
334
|
|
|
335
|
+
describe('bound $ methods', () => {
|
|
336
|
+
it('$build can be used as a detached function', () => {
|
|
337
|
+
const { $build } = schema
|
|
338
|
+
const result = $build({ text: 'Hello' })
|
|
339
|
+
expect(result).toEqual({
|
|
340
|
+
text: 'Hello',
|
|
341
|
+
$type: 'app.bsky.feed.post',
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('$isTypeOf can be used as a detached function', () => {
|
|
346
|
+
const { $isTypeOf } = schema
|
|
347
|
+
expect($isTypeOf({ text: 'Hello' })).toBe(true)
|
|
348
|
+
expect($isTypeOf({ $type: 'app.bsky.feed.post', text: 'Hello' })).toBe(
|
|
349
|
+
true,
|
|
350
|
+
)
|
|
351
|
+
expect($isTypeOf({ $type: 'other.type', text: 'Hello' })).toBe(false)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('$parse can be used as a detached function', () => {
|
|
355
|
+
const { $parse } = schema
|
|
356
|
+
const result = $parse({ text: 'Hello' })
|
|
357
|
+
expect(result).toEqual({ text: 'Hello' })
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('$matches can be used as a detached function', () => {
|
|
361
|
+
const { $matches } = schema
|
|
362
|
+
expect($matches({ text: 'Hello' })).toBe(true)
|
|
363
|
+
expect($matches(42)).toBe(false)
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
it('lazy property returns the same function on repeated access', () => {
|
|
367
|
+
const fn1 = schema.$build
|
|
368
|
+
const fn2 = schema.$build
|
|
369
|
+
expect(fn1).toBe(fn2)
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
|
|
335
373
|
describe('with complex nested schemas', () => {
|
|
336
374
|
const complexSchema = typedObject(
|
|
337
375
|
'app.bsky.actor.profile',
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
ValidationContext,
|
|
15
15
|
Validator,
|
|
16
16
|
} from '../core.js'
|
|
17
|
+
import { lazyProperty } from '../util/lazy-property.js'
|
|
17
18
|
|
|
18
19
|
export type MaybeTypedObject<
|
|
19
20
|
TType extends $Type,
|
|
@@ -56,10 +57,20 @@ export class TypedObjectSchema<
|
|
|
56
57
|
super()
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
61
|
+
if (!isPlainObject(input)) {
|
|
62
|
+
return ctx.issueUnexpectedType(input, 'object')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
'$type' in input &&
|
|
67
|
+
input.$type !== undefined &&
|
|
68
|
+
input.$type !== this.$type
|
|
69
|
+
) {
|
|
70
|
+
return ctx.issueInvalidPropertyValue(input, '$type', [this.$type])
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return ctx.validate(input, this.schema)
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
build(
|
|
@@ -71,32 +82,26 @@ export class TypedObjectSchema<
|
|
|
71
82
|
>
|
|
72
83
|
}
|
|
73
84
|
|
|
74
|
-
|
|
85
|
+
isTypeOf<TValue extends Record<string, unknown>>(
|
|
75
86
|
value: TValue,
|
|
76
87
|
): value is MaybeTypedObject<TType, TValue> {
|
|
77
|
-
return this
|
|
88
|
+
return value.$type === undefined || value.$type === this.$type
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Bound alias for {@link build} for compatibility with generated utilities.
|
|
93
|
+
* @see {@link build}
|
|
94
|
+
*/
|
|
95
|
+
get $build(): typeof this.build {
|
|
96
|
+
return lazyProperty(this, '$build', this.build.bind(this))
|
|
84
97
|
}
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
'$type' in input &&
|
|
93
|
-
input.$type !== undefined &&
|
|
94
|
-
input.$type !== this.$type
|
|
95
|
-
) {
|
|
96
|
-
return ctx.issueInvalidPropertyValue(input, '$type', [this.$type])
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return ctx.validate(input, this.schema)
|
|
99
|
+
/**
|
|
100
|
+
* Bound alias for {@link isTypeOf} for compatibility with generated utilities.
|
|
101
|
+
* @see {@link isTypeOf}
|
|
102
|
+
*/
|
|
103
|
+
get $isTypeOf(): typeof this.isTypeOf {
|
|
104
|
+
return lazyProperty(this, '$isTypeOf', this.isTypeOf.bind(this))
|
|
100
105
|
}
|
|
101
106
|
}
|
|
102
107
|
|
package/src/schema/union.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
InferInput,
|
|
3
3
|
InferOutput,
|
|
4
|
+
LexValidationError,
|
|
4
5
|
Schema,
|
|
5
6
|
ValidationContext,
|
|
6
|
-
ValidationError,
|
|
7
7
|
ValidationFailure,
|
|
8
8
|
Validator,
|
|
9
9
|
} from '../core.js'
|
|
@@ -53,7 +53,7 @@ export class UnionSchema<
|
|
|
53
53
|
failures.push(result)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
return ctx.failure(
|
|
56
|
+
return ctx.failure(LexValidationError.fromFailures(failures))
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|