@atproto/lex-schema 0.0.13 → 0.0.15
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 +39 -0
- package/dist/core/schema.d.ts +27 -35
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +73 -54
- package/dist/core/schema.js.map +1 -1
- package/dist/core/standard-schema.d.ts +14 -0
- package/dist/core/standard-schema.d.ts.map +1 -0
- package/dist/core/standard-schema.js +27 -0
- package/dist/core/standard-schema.js.map +1 -0
- package/dist/core/string-format.d.ts +4 -28
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +23 -17
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validation-error.d.ts +14 -6
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +18 -8
- package/dist/core/validation-error.js.map +1 -1
- package/dist/core/validation-issue.d.ts +15 -15
- package/dist/core/validation-issue.d.ts.map +1 -1
- package/dist/core/validation-issue.js +33 -29
- package/dist/core/validation-issue.js.map +1 -1
- package/dist/core/validator.d.ts +21 -18
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +5 -4
- package/dist/core/validator.js.map +1 -1
- package/dist/core.d.ts +0 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +0 -1
- package/dist/core.js.map +1 -1
- package/dist/schema/custom.d.ts +1 -1
- package/dist/schema/custom.d.ts.map +1 -1
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/never.d.ts +1 -1
- package/dist/schema/nullable.d.ts +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/ref.d.ts +1 -1
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/refine.d.ts +1 -1
- package/dist/schema/refine.d.ts.map +1 -1
- package/dist/schema/refine.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/typed-ref.d.ts +1 -1
- package/dist/schema/typed-union.d.ts +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/package.json +4 -3
- package/src/core/schema.ts +78 -69
- package/src/core/standard-schema.test.ts +124 -0
- package/src/core/standard-schema.ts +31 -0
- package/src/core/string-format.ts +26 -33
- package/src/core/validation-error.ts +25 -10
- package/src/core/validation-issue.ts +32 -32
- package/src/core/validator.ts +16 -12
- package/src/core.ts +0 -1
- package/src/schema/array.test.ts +2 -2
- package/src/schema/custom.ts +1 -7
- package/src/schema/params.test.ts +18 -2
- package/src/schema/params.ts +5 -2
- package/src/schema/record.ts +27 -22
- package/src/schema/ref.ts +1 -5
- package/src/schema/refine.ts +0 -1
- 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/dist/core/property-key.d.ts +0 -2
- package/dist/core/property-key.d.ts.map +0 -1
- package/dist/core/property-key.js +0 -3
- package/dist/core/property-key.js.map +0 -1
- package/src/core/property-key.ts +0 -1
package/src/core/schema.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec'
|
|
2
|
+
import { lazyProperty } from '../util/lazy-property.js'
|
|
3
|
+
import { StandardSchemaAdapter } from './standard-schema.js'
|
|
1
4
|
import {
|
|
2
5
|
InferInput,
|
|
3
6
|
InferOutput,
|
|
@@ -51,7 +54,6 @@ export interface SchemaInternals<out TInput = unknown, out TOutput = TInput> {
|
|
|
51
54
|
*
|
|
52
55
|
* @typeParam TInput - The type accepted as valid input during validation
|
|
53
56
|
* @typeParam TOutput - The type returned after parsing (may include transformations)
|
|
54
|
-
* @typeParam TInternals - Internal type structure for type inference
|
|
55
57
|
*
|
|
56
58
|
* @example
|
|
57
59
|
* ```typescript
|
|
@@ -66,19 +68,13 @@ export interface SchemaInternals<out TInput = unknown, out TOutput = TInput> {
|
|
|
66
68
|
*
|
|
67
69
|
* const schema = new MySchema()
|
|
68
70
|
* schema.assert('hello') // OK
|
|
69
|
-
* schema.assert(123) // Throws
|
|
71
|
+
* schema.assert(123) // Throws LexValidationError
|
|
70
72
|
* schema.matches('hello') // true
|
|
71
73
|
* schema.matches(123) // false
|
|
72
74
|
* ```
|
|
73
75
|
*/
|
|
74
|
-
export abstract class Schema<
|
|
75
|
-
|
|
76
|
-
out TOutput = TInput,
|
|
77
|
-
out TInternals extends SchemaInternals<TInput, TOutput> = SchemaInternals<
|
|
78
|
-
TInput,
|
|
79
|
-
TOutput
|
|
80
|
-
>,
|
|
81
|
-
> implements Validator<TInternals['input'], TInternals['output']>
|
|
76
|
+
export abstract class Schema<out TInput = unknown, out TOutput = TInput>
|
|
77
|
+
implements Validator<TInput, TOutput>, StandardSchemaV1<TInput, TOutput>
|
|
82
78
|
{
|
|
83
79
|
/**
|
|
84
80
|
* Internal phantom property for type inference.
|
|
@@ -86,7 +82,13 @@ export abstract class Schema<
|
|
|
86
82
|
*
|
|
87
83
|
* @internal
|
|
88
84
|
*/
|
|
89
|
-
declare readonly ['__lex']:
|
|
85
|
+
declare readonly ['__lex']: SchemaInternals<TInput, TOutput>
|
|
86
|
+
|
|
87
|
+
get '~standard'(): StandardSchemaV1.Props<TInput, TOutput> {
|
|
88
|
+
// Lazily create, and cache, the Standard Schema adapter for this schema
|
|
89
|
+
// instance.
|
|
90
|
+
return lazyProperty(this, '~standard', new StandardSchemaAdapter(this))
|
|
91
|
+
}
|
|
90
92
|
|
|
91
93
|
// Needed to discriminate multiple schema types when used in unions. Without
|
|
92
94
|
// this, Typescript could allow an EnumSchema<"foo" | "bar"> to be used where
|
|
@@ -190,12 +192,12 @@ export abstract class Schema<
|
|
|
190
192
|
*
|
|
191
193
|
* Unlike {@link validate}, this method allows the schema to transform
|
|
192
194
|
* the input value (e.g., applying default values, type coercion).
|
|
193
|
-
* Throws a {@link
|
|
195
|
+
* Throws a {@link LexValidationError} if the input is invalid.
|
|
194
196
|
*
|
|
195
197
|
* @param input - The value to parse
|
|
196
198
|
* @param options - Optional parsing configuration
|
|
197
199
|
* @returns The parsed and potentially transformed value
|
|
198
|
-
* @throws {
|
|
200
|
+
* @throws {LexValidationError} If the input fails validation
|
|
199
201
|
*
|
|
200
202
|
* @example
|
|
201
203
|
* ```typescript
|
|
@@ -244,13 +246,13 @@ export abstract class Schema<
|
|
|
244
246
|
*
|
|
245
247
|
* Unlike {@link parse}, this method requires the input to exactly match
|
|
246
248
|
* the schema without any transformations (no defaults applied, no coercion).
|
|
247
|
-
* Throws a {@link
|
|
249
|
+
* Throws a {@link LexValidationError} if the input is invalid or would require transformation.
|
|
248
250
|
*
|
|
249
251
|
* @typeParam I - The input type (preserved in the return type)
|
|
250
252
|
* @param input - The value to validate
|
|
251
253
|
* @param options - Optional validation configuration
|
|
252
254
|
* @returns The validated input with narrowed type
|
|
253
|
-
* @throws {
|
|
255
|
+
* @throws {LexValidationError} If the input fails validation or requires transformation
|
|
254
256
|
*
|
|
255
257
|
* @example
|
|
256
258
|
* ```typescript
|
|
@@ -297,104 +299,111 @@ export abstract class Schema<
|
|
|
297
299
|
|
|
298
300
|
// @NOTE Dollar-prefixed aliases
|
|
299
301
|
//
|
|
300
|
-
// The
|
|
301
|
-
// the schema's methods without the need to specify ".main."
|
|
302
|
-
// namespace. This
|
|
303
|
-
// like "app.bsky.feed.post.<utility>()" instead of
|
|
304
|
-
// "app.bsky.feed.post.main.<utility>()".
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
302
|
+
// The `lex-builder` lib generates namespaced utility functions that allow
|
|
303
|
+
// accessing the schema's methods without the need to specify the ".main."
|
|
304
|
+
// part of the namespace. This allows utilities for a particular record type
|
|
305
|
+
// to be called like "app.bsky.feed.post.<utility>()" instead of
|
|
306
|
+
// "app.bsky.feed.post.main.<utility>()".
|
|
307
|
+
//
|
|
308
|
+
// Because those utilities could conflict with other schemas (e.g. if there is
|
|
309
|
+
// a lexicon definition with the same name as the "<utility>"), those exported
|
|
310
|
+
// utilities will be prefixed with "$".
|
|
311
|
+
//
|
|
312
|
+
// Similarly, since those utilities are defined as simple "const", they are
|
|
313
|
+
// also bound (using JS's .bind) to the schema instance, so that they can be
|
|
314
|
+
// used without worrying about the context (e.g. "app.bsky.feed.post.$parse()"
|
|
315
|
+
// will work regardless of how it is imported or called).
|
|
316
|
+
//
|
|
317
|
+
// In order to provide the same functionalities for non-main definitions, we
|
|
318
|
+
// also define those aliases directly on the schema instance, so that they can
|
|
319
|
+
// be used in the same way as the utilities generated by "lex-builder". For
|
|
320
|
+
// example, if there is a non-main definition "app.bsky.feed.defs.postView",
|
|
321
|
+
// it will also be possible to call "app.bsky.feed.defs.postView.$parse()".
|
|
322
|
+
//
|
|
323
|
+
// These methods are also "bound" to the instance so that they can be used
|
|
324
|
+
// exactly like the utilities generated by "lex-builder", without worrying
|
|
325
|
+
// about the context.
|
|
310
326
|
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
327
|
+
// There are two ways we could "bind" those methods to the instance:
|
|
328
|
+
// 1. Define them as getters that return the bound method (e.g. get $parse() {
|
|
329
|
+
// return this.parse.bind(this) })
|
|
330
|
+
// 2. Define them as properties that are initialized in the constructor (e.g.
|
|
331
|
+
// this.$parse = this.parse.bind(this))
|
|
332
|
+
//
|
|
333
|
+
// Since a **lot** of those methods would end-up being created in systems that
|
|
334
|
+
// contains many schemas (e.g. the appview), we choose the first approach
|
|
335
|
+
// (getters) in order to avoid the overhead of creating all those bound
|
|
336
|
+
// functions upfront when instantiating the schemas.
|
|
313
337
|
|
|
314
338
|
/**
|
|
315
|
-
*
|
|
316
|
-
*
|
|
339
|
+
* Bound alias for {@link assert} for compatibility with generated utilities.
|
|
317
340
|
* @see {@link assert}
|
|
318
341
|
*/
|
|
319
|
-
$assert(
|
|
320
|
-
return this.assert(
|
|
342
|
+
get $assert(): typeof this.assert {
|
|
343
|
+
return lazyProperty(this, '$assert', this.assert.bind(this))
|
|
321
344
|
}
|
|
322
345
|
|
|
323
346
|
/**
|
|
324
|
-
*
|
|
325
|
-
*
|
|
347
|
+
* Bound alias for {@link check} for compatibility with generated utilities.
|
|
326
348
|
* @see {@link check}
|
|
327
349
|
*/
|
|
328
|
-
$check(
|
|
329
|
-
return this.check(
|
|
350
|
+
get $check(): typeof this.check {
|
|
351
|
+
return lazyProperty(this, '$check', this.check.bind(this))
|
|
330
352
|
}
|
|
331
353
|
|
|
332
354
|
/**
|
|
333
|
-
*
|
|
334
|
-
*
|
|
355
|
+
* Bound alias for {@link cast} for compatibility with generated utilities.
|
|
335
356
|
* @see {@link cast}
|
|
336
357
|
*/
|
|
337
|
-
$cast
|
|
338
|
-
return this.cast(
|
|
358
|
+
get $cast(): typeof this.cast {
|
|
359
|
+
return lazyProperty(this, '$cast', this.cast.bind(this))
|
|
339
360
|
}
|
|
340
361
|
|
|
341
362
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
363
|
+
* Bound alias for {@link matches} for compatibility with generated utilities.
|
|
344
364
|
* @see {@link matches}
|
|
345
365
|
*/
|
|
346
|
-
$matches(
|
|
347
|
-
return this.matches(
|
|
366
|
+
get $matches(): typeof this.matches {
|
|
367
|
+
return lazyProperty(this, '$matches', this.matches.bind(this))
|
|
348
368
|
}
|
|
349
369
|
|
|
350
370
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
371
|
+
* Bound alias for {@link ifMatches} for compatibility with generated utilities.
|
|
353
372
|
* @see {@link ifMatches}
|
|
354
373
|
*/
|
|
355
|
-
$ifMatches
|
|
356
|
-
return this.ifMatches(
|
|
374
|
+
get $ifMatches(): typeof this.ifMatches {
|
|
375
|
+
return lazyProperty(this, '$ifMatches', this.ifMatches.bind(this))
|
|
357
376
|
}
|
|
358
377
|
|
|
359
378
|
/**
|
|
360
|
-
*
|
|
361
|
-
*
|
|
379
|
+
* Bound alias for {@link parse} for compatibility with generated utilities.
|
|
362
380
|
* @see {@link parse}
|
|
363
381
|
*/
|
|
364
|
-
$parse(
|
|
365
|
-
return this.parse(
|
|
382
|
+
get $parse(): typeof this.parse {
|
|
383
|
+
return lazyProperty(this, '$parse', this.parse.bind(this))
|
|
366
384
|
}
|
|
367
385
|
|
|
368
386
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
387
|
+
* Bound alias for {@link safeParse} for compatibility with generated utilities.
|
|
371
388
|
* @see {@link safeParse}
|
|
372
389
|
*/
|
|
373
|
-
$safeParse(
|
|
374
|
-
|
|
375
|
-
options?: ValidateOptions,
|
|
376
|
-
): ValidationResult<InferOutput<this>> {
|
|
377
|
-
return this.safeParse(input, options)
|
|
390
|
+
get $safeParse(): typeof this.safeParse {
|
|
391
|
+
return lazyProperty(this, '$safeParse', this.safeParse.bind(this))
|
|
378
392
|
}
|
|
379
393
|
|
|
380
394
|
/**
|
|
381
|
-
*
|
|
382
|
-
*
|
|
395
|
+
* Bound alias for {@link validate} for compatibility with generated utilities.
|
|
383
396
|
* @see {@link validate}
|
|
384
397
|
*/
|
|
385
|
-
$validate
|
|
386
|
-
return this.validate(
|
|
398
|
+
get $validate(): typeof this.validate {
|
|
399
|
+
return lazyProperty(this, '$validate', this.validate.bind(this))
|
|
387
400
|
}
|
|
388
401
|
|
|
389
402
|
/**
|
|
390
|
-
*
|
|
391
|
-
*
|
|
403
|
+
* Bound alias for {@link safeValidate} for compatibility with generated utilities.
|
|
392
404
|
* @see {@link safeValidate}
|
|
393
405
|
*/
|
|
394
|
-
$safeValidate
|
|
395
|
-
|
|
396
|
-
options?: ValidateOptions,
|
|
397
|
-
): ValidationResult<I & InferInput<this>> {
|
|
398
|
-
return this.safeValidate(input, options)
|
|
406
|
+
get $safeValidate(): typeof this.safeValidate {
|
|
407
|
+
return lazyProperty(this, '$safeValidate', this.safeValidate.bind(this))
|
|
399
408
|
}
|
|
400
409
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { assert, describe, expect, it } from 'vitest'
|
|
2
|
+
import { array } from '../schema/array.js'
|
|
3
|
+
import { integer } from '../schema/integer.js'
|
|
4
|
+
import { object } from '../schema/object.js'
|
|
5
|
+
import { optional } from '../schema/optional.js'
|
|
6
|
+
import { string } from '../schema/string.js'
|
|
7
|
+
import { withDefault } from '../schema/with-default.js'
|
|
8
|
+
import { LexValidationError } from './validation-error.js'
|
|
9
|
+
|
|
10
|
+
describe('StandardSchemaAdapter', () => {
|
|
11
|
+
describe('metadata', () => {
|
|
12
|
+
const schema = integer()
|
|
13
|
+
|
|
14
|
+
it('has version 1', () => {
|
|
15
|
+
expect(schema['~standard'].version).toBe(1)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('has vendor @atproto/lex-schema', () => {
|
|
19
|
+
expect(schema['~standard'].vendor).toBe('@atproto/lex-schema')
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('lazy caching', () => {
|
|
24
|
+
it('returns the same adapter instance on repeated accesses', () => {
|
|
25
|
+
const schema = integer()
|
|
26
|
+
const first = schema['~standard']
|
|
27
|
+
const second = schema['~standard']
|
|
28
|
+
expect(first).toBe(second)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('validate() result shape on success', () => {
|
|
33
|
+
it('returns a value property for a valid integer', () => {
|
|
34
|
+
const result = integer()['~standard'].validate(42)
|
|
35
|
+
expect(result).toMatchObject({ value: 42 })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('returns a value property for a valid string', () => {
|
|
39
|
+
const result = string()['~standard'].validate('hello')
|
|
40
|
+
expect(result).toMatchObject({ value: 'hello' })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('does not include an issues property on success', () => {
|
|
44
|
+
const result = integer()['~standard'].validate(1)
|
|
45
|
+
expect(result).not.toHaveProperty('issues')
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('validate() result shape on failure', () => {
|
|
50
|
+
it('returns a LexValidationError with issues for an invalid value', () => {
|
|
51
|
+
const result = integer()['~standard'].validate('not-a-number')
|
|
52
|
+
assert(result instanceof LexValidationError)
|
|
53
|
+
expect(Array.isArray(result.issues)).toBe(true)
|
|
54
|
+
expect(result.issues.length).toBeGreaterThan(0)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('does not include a value property on failure', () => {
|
|
58
|
+
const result = integer()['~standard'].validate('not-a-number')
|
|
59
|
+
expect(result).not.toHaveProperty('value')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('issues[].message', () => {
|
|
63
|
+
it('is a non-empty string', () => {
|
|
64
|
+
const result = integer()['~standard'].validate('not-a-number')
|
|
65
|
+
assert(result instanceof LexValidationError)
|
|
66
|
+
for (const issue of result.issues) {
|
|
67
|
+
expect(typeof issue.message).toBe('string')
|
|
68
|
+
expect(issue.message.length).toBeGreaterThan(0)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('describes the type mismatch', () => {
|
|
73
|
+
const result = integer()['~standard'].validate('not-a-number')
|
|
74
|
+
assert(result instanceof LexValidationError)
|
|
75
|
+
expect(result.issues[0].message).toContain('integer')
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('issues[].path', () => {
|
|
80
|
+
it('is an empty array for a root-level failure', () => {
|
|
81
|
+
const result = integer()['~standard'].validate('not-a-number')
|
|
82
|
+
assert(result instanceof LexValidationError)
|
|
83
|
+
expect(result.issues[0].path).toEqual([])
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('reflects the property key for a nested object failure', () => {
|
|
87
|
+
const schema = object({ age: integer() })
|
|
88
|
+
const result = schema['~standard'].validate({ age: 'not-a-number' })
|
|
89
|
+
assert(result instanceof LexValidationError)
|
|
90
|
+
expect(result.issues[0].path).toContain('age')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('reflects the index for an array element failure', () => {
|
|
94
|
+
const schema = array(integer())
|
|
95
|
+
const result = schema['~standard'].validate([1, 'bad', 3])
|
|
96
|
+
assert(result instanceof LexValidationError)
|
|
97
|
+
expect(result.issues[0].path).toContain(1)
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('parse mode (default value application)', () => {
|
|
103
|
+
it('applies default values when input is undefined', () => {
|
|
104
|
+
const schema = withDefault(integer(), 10)
|
|
105
|
+
const result = schema['~standard'].validate(undefined)
|
|
106
|
+
expect(result).toMatchObject({ value: 10 })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('uses the provided value instead of default when input is present', () => {
|
|
110
|
+
const schema = withDefault(integer(), 10)
|
|
111
|
+
const result = schema['~standard'].validate(42)
|
|
112
|
+
expect(result).toMatchObject({ value: 42 })
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('applies defaults for optional object properties in parse mode', () => {
|
|
116
|
+
const schema = object({
|
|
117
|
+
name: string(),
|
|
118
|
+
count: optional(withDefault(integer(), 0)),
|
|
119
|
+
})
|
|
120
|
+
const result = schema['~standard'].validate({ name: 'Alice' })
|
|
121
|
+
expect(result).toMatchObject({ value: { name: 'Alice', count: 0 } })
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec'
|
|
2
|
+
import { ValidationContext, Validator } from './validator.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The Standard Schema adapter for {@link Validator} instances.
|
|
6
|
+
*/
|
|
7
|
+
export class StandardSchemaAdapter<TInput, TOutput>
|
|
8
|
+
implements StandardSchemaV1.Props<TInput, TOutput>
|
|
9
|
+
{
|
|
10
|
+
readonly version = 1
|
|
11
|
+
|
|
12
|
+
readonly vendor = '@atproto/lex-schema'
|
|
13
|
+
|
|
14
|
+
declare readonly types: StandardSchemaV1.Types<TInput, TOutput>
|
|
15
|
+
|
|
16
|
+
constructor(private readonly validator: Validator<TInput, TOutput>) {}
|
|
17
|
+
|
|
18
|
+
validate(
|
|
19
|
+
value: unknown,
|
|
20
|
+
options?: StandardSchemaV1.Options,
|
|
21
|
+
): StandardSchemaV1.Result<TOutput> {
|
|
22
|
+
// Perform validation in "parse" mode to ensure transformations (defaults,
|
|
23
|
+
// coercions, etc.) are applied. Also ensures that the output type is
|
|
24
|
+
// returned. Note that ValidationResult is compatible with
|
|
25
|
+
// StandardSchemaV1.Result :-)
|
|
26
|
+
return ValidationContext.validate(value, this.validator, {
|
|
27
|
+
...options?.libraryOptions,
|
|
28
|
+
mode: 'parse',
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
RecordKeyString,
|
|
10
10
|
TidString,
|
|
11
11
|
UriString,
|
|
12
|
-
|
|
12
|
+
isAtIdentifierString,
|
|
13
|
+
isDatetimeString,
|
|
13
14
|
isValidAtUri,
|
|
14
|
-
isValidDatetime,
|
|
15
15
|
isValidDid,
|
|
16
16
|
isValidHandle,
|
|
17
17
|
isValidLanguage,
|
|
@@ -26,21 +26,30 @@ import { CheckFn } from '../util/assertion-util.js'
|
|
|
26
26
|
// Individual string format types and type guards
|
|
27
27
|
// -----------------------------------------------------------------------------
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
// Re-exporting from @atproto/syntax without modification to preserve types and
|
|
30
|
+
// documentation for types and utilities that are already well-defined there.
|
|
31
|
+
// @TODO rework other string formats in @atproto/syntax to follow this pattern
|
|
32
|
+
// and re-export here, e.g. language tags, NSIDs, record keys, etc.
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
type AtIdentifierString,
|
|
36
|
+
asAtIdentifierString,
|
|
37
|
+
ifAtIdentifierString,
|
|
38
|
+
isAtIdentifierString,
|
|
39
|
+
} from '@atproto/syntax'
|
|
40
|
+
|
|
41
|
+
// AtIdentifierString utilities
|
|
42
|
+
export { isDidIdentifier, isHandleIdentifier } from '@atproto/syntax'
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
type DatetimeString,
|
|
46
|
+
asDatetimeString,
|
|
47
|
+
ifDatetimeString,
|
|
48
|
+
isDatetimeString,
|
|
49
|
+
} from '@atproto/syntax'
|
|
50
|
+
|
|
51
|
+
// DatetimeString utilities
|
|
52
|
+
export { currentDatetimeString, toDatetimeString } from '@atproto/syntax'
|
|
44
53
|
|
|
45
54
|
/**
|
|
46
55
|
* Type guard that checks if a value is a valid AT URI.
|
|
@@ -74,22 +83,6 @@ export const isCidString = ((v) => validateCidString(v)) as CheckFn<CidString>
|
|
|
74
83
|
*/
|
|
75
84
|
export type CidString = string
|
|
76
85
|
|
|
77
|
-
/**
|
|
78
|
-
* Type guard that checks if a value is a valid datetime string.
|
|
79
|
-
*
|
|
80
|
-
* @param value - The value to check
|
|
81
|
-
* @returns `true` if the value is a valid datetime string
|
|
82
|
-
*/
|
|
83
|
-
export const isDatetimeString: CheckFn<DatetimeString> = isValidDatetime
|
|
84
|
-
export type {
|
|
85
|
-
/**
|
|
86
|
-
* An ISO 8601 datetime string.
|
|
87
|
-
*
|
|
88
|
-
* @example `"2024-01-15T12:30:00.000Z"`
|
|
89
|
-
*/
|
|
90
|
-
DatetimeString,
|
|
91
|
-
}
|
|
92
|
-
|
|
93
86
|
/**
|
|
94
87
|
* Type guard that checks if a value is a valid DID string.
|
|
95
88
|
*
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
21
|
* ```typescript
|
|
22
|
-
* const error = new
|
|
22
|
+
* const error = new LexValidationError([
|
|
23
23
|
* new IssueInvalidType(['user', 'age'], 'hello', ['number'])
|
|
24
24
|
* ])
|
|
25
25
|
* console.log(error.message)
|
|
@@ -29,9 +29,16 @@ import {
|
|
|
29
29
|
* console.log(error.toJSON())
|
|
30
30
|
* // { error: 'InvalidRequest', message: '...', issues: [...] }
|
|
31
31
|
* ```
|
|
32
|
+
*
|
|
33
|
+
* @note this class implements {@link ResultFailure} to allow it to be used
|
|
34
|
+
* directly as a failure reason in validation results, avoiding the need for
|
|
35
|
+
* wrapping it in an additional object.
|
|
32
36
|
*/
|
|
33
|
-
export class
|
|
34
|
-
|
|
37
|
+
export class LexValidationError
|
|
38
|
+
extends LexError<'InvalidRequest'>
|
|
39
|
+
implements ResultFailure<LexValidationError>
|
|
40
|
+
{
|
|
41
|
+
name = 'LexValidationError'
|
|
35
42
|
|
|
36
43
|
/**
|
|
37
44
|
* The list of validation issues that caused this error.
|
|
@@ -56,12 +63,20 @@ export class ValidationError extends LexError {
|
|
|
56
63
|
this.issues = issuesAgg
|
|
57
64
|
}
|
|
58
65
|
|
|
66
|
+
/** @see {ResultFailure.success} */
|
|
67
|
+
readonly success = false as const
|
|
68
|
+
|
|
69
|
+
/** @see {ResultFailure.reason} */
|
|
70
|
+
get reason() {
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
59
74
|
/**
|
|
60
75
|
* Converts the error to a JSON-serializable object.
|
|
61
76
|
*
|
|
62
|
-
* @returns An object containing the error details and
|
|
77
|
+
* @returns An object containing the error details and issues details
|
|
63
78
|
*/
|
|
64
|
-
toJSON() {
|
|
79
|
+
override toJSON() {
|
|
65
80
|
return {
|
|
66
81
|
...super.toJSON(),
|
|
67
82
|
issues: this.issues.map((issue) => issue.toJSON()),
|
|
@@ -81,23 +96,23 @@ export class ValidationError extends LexError {
|
|
|
81
96
|
* ```typescript
|
|
82
97
|
* const failures = schemas.map(s => s.safeValidate(data)).filter(r => !r.success)
|
|
83
98
|
* if (failures.length === schemas.length) {
|
|
84
|
-
* throw
|
|
99
|
+
* throw LexValidationError.fromFailures(failures)
|
|
85
100
|
* }
|
|
86
101
|
* ```
|
|
87
102
|
*/
|
|
88
103
|
static fromFailures(
|
|
89
|
-
failures: ResultFailure<
|
|
90
|
-
):
|
|
104
|
+
failures: readonly ResultFailure<LexValidationError>[],
|
|
105
|
+
): LexValidationError {
|
|
91
106
|
if (failures.length === 1) return failureReason(failures[0])
|
|
92
107
|
const issues = failures.flatMap(extractFailureIssues)
|
|
93
|
-
return new
|
|
108
|
+
return new LexValidationError(issues, {
|
|
94
109
|
// Keep the original errors as the cause chain
|
|
95
110
|
cause: failures.map(failureReason),
|
|
96
111
|
})
|
|
97
112
|
}
|
|
98
113
|
}
|
|
99
114
|
|
|
100
|
-
function extractFailureIssues(result: ResultFailure<
|
|
115
|
+
function extractFailureIssues(result: ResultFailure<LexValidationError>) {
|
|
101
116
|
return result.reason.issues
|
|
102
117
|
}
|
|
103
118
|
|