@atproto/lex-schema 0.0.14 → 0.0.16
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 +30 -0
- package/dist/core/schema.d.ts +5 -4
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +7 -2
- 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 +24 -17
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +57 -30
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validation-error.d.ts +10 -2
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +10 -0
- 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 +29 -14
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +4 -2
- 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/blob.d.ts +10 -8
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +39 -14
- package/dist/schema/blob.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/payload.d.ts +2 -2
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/record.d.ts +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/string.js +1 -1
- package/dist/schema/string.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/package.json +5 -3
- package/src/core/schema.ts +12 -11
- package/src/core/standard-schema.test.ts +124 -0
- package/src/core/standard-schema.ts +31 -0
- package/src/core/string-format.ts +73 -31
- package/src/core/validation-error.ts +16 -1
- package/src/core/validation-issue.ts +32 -32
- package/src/core/validator.ts +26 -6
- package/src/core.ts +0 -1
- package/src/schema/array.test.ts +2 -2
- package/src/schema/blob.test.ts +317 -49
- package/src/schema/blob.ts +56 -23
- package/src/schema/custom.ts +1 -7
- package/src/schema/params.test.ts +2 -2
- package/src/schema/payload.ts +2 -2
- package/src/schema/ref.ts +1 -5
- package/src/schema/refine.ts +0 -1
- package/src/schema/string.test.ts +63 -0
- package/src/schema/string.ts +1 -1
- 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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ifCid, isLegacyBlobRef, isPlainObject } from '@atproto/lex-data'
|
|
2
|
-
import { PropertyKey } from './property-key.js'
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Abstract base class for all validation issues.
|
|
@@ -9,10 +8,13 @@ import { PropertyKey } from './property-key.js'
|
|
|
9
8
|
* - The path to the invalid value in the data structure
|
|
10
9
|
* - The actual input value that failed validation
|
|
11
10
|
*
|
|
12
|
-
* Subclasses add specific properties relevant to each issue type and
|
|
13
|
-
*
|
|
11
|
+
* Subclasses add specific properties relevant to each issue type and implement
|
|
12
|
+
* the {@link message} property for human-readable error messages (that don't
|
|
13
|
+
* contain the error path)
|
|
14
14
|
*/
|
|
15
15
|
export abstract class Issue {
|
|
16
|
+
abstract readonly message: string
|
|
17
|
+
|
|
16
18
|
constructor(
|
|
17
19
|
readonly code: string,
|
|
18
20
|
readonly path: readonly PropertyKey[],
|
|
@@ -22,7 +24,9 @@ export abstract class Issue {
|
|
|
22
24
|
/**
|
|
23
25
|
* Returns a human-readable description of the validation issue.
|
|
24
26
|
*/
|
|
25
|
-
|
|
27
|
+
toString() {
|
|
28
|
+
return `${this.message}${stringifyPath(this.path)}`
|
|
29
|
+
}
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
32
|
* Converts the issue to a JSON-serializable object.
|
|
@@ -33,7 +37,7 @@ export abstract class Issue {
|
|
|
33
37
|
return {
|
|
34
38
|
code: this.code,
|
|
35
39
|
path: this.path,
|
|
36
|
-
message: this.
|
|
40
|
+
message: this.message,
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
}
|
|
@@ -51,10 +55,6 @@ export class IssueCustom extends Issue {
|
|
|
51
55
|
) {
|
|
52
56
|
super('custom', path, input)
|
|
53
57
|
}
|
|
54
|
-
|
|
55
|
-
toString() {
|
|
56
|
-
return `${this.message}${stringifyPath(this.path)}`
|
|
57
|
-
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
@@ -67,20 +67,13 @@ export class IssueInvalidFormat extends Issue {
|
|
|
67
67
|
path: readonly PropertyKey[],
|
|
68
68
|
input: unknown,
|
|
69
69
|
readonly format: string,
|
|
70
|
-
readonly
|
|
70
|
+
readonly detail?: string,
|
|
71
71
|
) {
|
|
72
72
|
super('invalid_format', path, input)
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
return `Invalid ${this.formatDescription}${this.
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
toJSON() {
|
|
80
|
-
return {
|
|
81
|
-
...super.toJSON(),
|
|
82
|
-
format: this.format,
|
|
83
|
-
}
|
|
75
|
+
override get message(): string {
|
|
76
|
+
return `Invalid ${this.formatDescription}${this.detail ? ` (${this.detail}, ` : ' ('}got ${stringifyValue(this.input)})`
|
|
84
77
|
}
|
|
85
78
|
|
|
86
79
|
/** Returns a human-readable description of the expected format. */
|
|
@@ -102,6 +95,13 @@ export class IssueInvalidFormat extends Issue {
|
|
|
102
95
|
return this.format
|
|
103
96
|
}
|
|
104
97
|
}
|
|
98
|
+
|
|
99
|
+
toJSON() {
|
|
100
|
+
return {
|
|
101
|
+
...super.toJSON(),
|
|
102
|
+
format: this.format,
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
@@ -119,8 +119,8 @@ export class IssueInvalidType extends Issue {
|
|
|
119
119
|
super('invalid_type', path, input)
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
return `Expected ${oneOf(this.expected.map(stringifyExpectedType))} value type
|
|
122
|
+
override get message(): string {
|
|
123
|
+
return `Expected ${oneOf(this.expected.map(stringifyExpectedType))} value type (got ${stringifyType(this.input)})`
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
toJSON() {
|
|
@@ -146,8 +146,8 @@ export class IssueInvalidValue extends Issue {
|
|
|
146
146
|
super('invalid_value', path, input)
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
return `Expected ${oneOf(this.values.map(stringifyValue))}
|
|
149
|
+
override get message(): string {
|
|
150
|
+
return `Expected ${oneOf(this.values.map(stringifyValue))} (got ${stringifyValue(this.input)})`
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
toJSON() {
|
|
@@ -170,8 +170,8 @@ export class IssueRequiredKey extends Issue {
|
|
|
170
170
|
super('required_key', path, input)
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
return `Missing required key "${String(this.key)}"
|
|
173
|
+
override get message(): string {
|
|
174
|
+
return `Missing required key "${String(this.key)}"`
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
toJSON() {
|
|
@@ -214,8 +214,8 @@ export class IssueTooBig extends Issue {
|
|
|
214
214
|
super('too_big', path, input)
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
|
|
218
|
-
return `${this.type} too big (maximum ${this.maximum}
|
|
217
|
+
override get message(): string {
|
|
218
|
+
return `${this.type} too big (maximum ${this.maximum}, got ${this.actual})`
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
toJSON() {
|
|
@@ -241,8 +241,8 @@ export class IssueTooSmall extends Issue {
|
|
|
241
241
|
super('too_small', path, input)
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
|
|
245
|
-
return `${this.type} too small (minimum ${this.minimum}
|
|
244
|
+
override get message(): string {
|
|
245
|
+
return `${this.type} too small (minimum ${this.minimum}, got ${this.actual})`
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
toJSON() {
|
|
@@ -274,9 +274,9 @@ function buildJsonPath(path: readonly PropertyKey[]): string {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
function toJsonPathSegment(segment: PropertyKey): string {
|
|
277
|
-
if (typeof segment === 'number') {
|
|
278
|
-
return `[${segment}]`
|
|
279
|
-
} else if (/^[a-zA-Z_$][a-zA-Z0-9_]*$/.test(segment
|
|
277
|
+
if (typeof segment === 'number' || typeof segment === 'symbol') {
|
|
278
|
+
return `[${String(segment)}]`
|
|
279
|
+
} else if (/^[a-zA-Z_$][a-zA-Z0-9_]*$/.test(segment)) {
|
|
280
280
|
return `.${segment}`
|
|
281
281
|
} else {
|
|
282
282
|
return `[${JSON.stringify(segment)}]`
|
package/src/core/validator.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import { ResultFailure, ResultSuccess,
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2
|
+
import { ResultFailure, ResultSuccess, success } from './result.js'
|
|
3
3
|
import { LexValidationError } from './validation-error.js'
|
|
4
4
|
import {
|
|
5
5
|
Issue,
|
|
@@ -21,8 +21,12 @@ export type ValidationSuccess<Value = unknown> = ResultSuccess<Value>
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Represents a failed validation result containing a {@link LexValidationError}.
|
|
24
|
+
*
|
|
25
|
+
* @extends ResultFailure<LexValidationError>
|
|
26
|
+
* @see {@link ResultFailure}
|
|
27
|
+
* @see {@link LexValidationError}
|
|
24
28
|
*/
|
|
25
|
-
export type ValidationFailure =
|
|
29
|
+
export type ValidationFailure = LexValidationError
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
32
|
* Discriminated union representing the outcome of a validation operation.
|
|
@@ -148,11 +152,13 @@ export type ValidationOptions = {
|
|
|
148
152
|
/**
|
|
149
153
|
* The validation mode determining how transformations are handled.
|
|
150
154
|
*
|
|
151
|
-
* - `"validate"
|
|
155
|
+
* - `"validate"`: Strict validation where the result must be
|
|
152
156
|
* strictly equal to the input value. No transformations such as applying
|
|
153
157
|
* default values are allowed.
|
|
154
158
|
* - `"parse"`: Allows the schema to transform the input value, such as
|
|
155
159
|
* applying default values or performing type coercion.
|
|
160
|
+
*
|
|
161
|
+
* @default "validate"
|
|
156
162
|
*/
|
|
157
163
|
mode?: 'validate' | 'parse'
|
|
158
164
|
|
|
@@ -169,6 +175,17 @@ export type ValidationOptions = {
|
|
|
169
175
|
* ```
|
|
170
176
|
*/
|
|
171
177
|
path?: readonly PropertyKey[]
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Whether to enforce strict validation rules (e.g., MIME type matching, size
|
|
181
|
+
* limits, datetime format).
|
|
182
|
+
*
|
|
183
|
+
* This is typically useful to allow more lax validation when parsing server
|
|
184
|
+
* responses, while enforcing strict validation for user input.
|
|
185
|
+
*
|
|
186
|
+
* @default true
|
|
187
|
+
*/
|
|
188
|
+
strict?: boolean
|
|
172
189
|
}
|
|
173
190
|
|
|
174
191
|
/**
|
|
@@ -217,6 +234,7 @@ export class ValidationContext {
|
|
|
217
234
|
mode: 'parse'
|
|
218
235
|
},
|
|
219
236
|
): ValidationResult<InferOutput<V>>
|
|
237
|
+
|
|
220
238
|
/**
|
|
221
239
|
* Validates input against a validator in validate mode (default).
|
|
222
240
|
*
|
|
@@ -237,6 +255,7 @@ export class ValidationContext {
|
|
|
237
255
|
mode?: 'validate'
|
|
238
256
|
},
|
|
239
257
|
): ValidationResult<I & InferInput<V>>
|
|
258
|
+
|
|
240
259
|
/**
|
|
241
260
|
* Validates input against a validator with configurable options.
|
|
242
261
|
*
|
|
@@ -258,6 +277,7 @@ export class ValidationContext {
|
|
|
258
277
|
const context = new ValidationContext({
|
|
259
278
|
path: options?.path ?? [],
|
|
260
279
|
mode: options?.mode ?? 'validate',
|
|
280
|
+
strict: options?.strict ?? true,
|
|
261
281
|
})
|
|
262
282
|
return context.validate(input, validator)
|
|
263
283
|
}
|
|
@@ -326,7 +346,7 @@ export class ValidationContext {
|
|
|
326
346
|
if (this.issues.length > 0) {
|
|
327
347
|
// Validator returned a success but issues were added via the context.
|
|
328
348
|
// This means the overall validation failed.
|
|
329
|
-
return
|
|
349
|
+
return new LexValidationError(Array.from(this.issues))
|
|
330
350
|
}
|
|
331
351
|
|
|
332
352
|
if (this.options.mode !== 'parse' && !Object.is(result.value, input)) {
|
|
@@ -426,7 +446,7 @@ export class ValidationContext {
|
|
|
426
446
|
* @returns A failed validation result
|
|
427
447
|
*/
|
|
428
448
|
failure(reason: LexValidationError): ValidationFailure {
|
|
429
|
-
return
|
|
449
|
+
return reason
|
|
430
450
|
}
|
|
431
451
|
|
|
432
452
|
/**
|
package/src/core.ts
CHANGED
package/src/schema/array.test.ts
CHANGED
|
@@ -77,11 +77,11 @@ describe('ArraySchema', () => {
|
|
|
77
77
|
it('rejects single values', () => {
|
|
78
78
|
const schema = array(string())
|
|
79
79
|
const result = schema.safeValidate(3)
|
|
80
|
-
expect(result).
|
|
80
|
+
expect(result).toMatchObject({
|
|
81
81
|
success: false,
|
|
82
82
|
reason: expect.objectContaining({
|
|
83
83
|
message: expect.stringContaining(
|
|
84
|
-
'Expected array value type
|
|
84
|
+
'Expected array value type (got integer) at $',
|
|
85
85
|
),
|
|
86
86
|
}),
|
|
87
87
|
})
|
package/src/schema/blob.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
1
|
+
import { assert, describe, expect, it } from 'vitest'
|
|
2
2
|
import { parseCid } from '@atproto/lex-data'
|
|
3
3
|
import { blob } from './blob.js'
|
|
4
4
|
|
|
@@ -214,37 +214,62 @@ describe('BlobSchema', () => {
|
|
|
214
214
|
})
|
|
215
215
|
|
|
216
216
|
describe('strict validation', () => {
|
|
217
|
-
const
|
|
217
|
+
const schema = blob()
|
|
218
218
|
|
|
219
219
|
it('accepts valid raw CID in strict mode', () => {
|
|
220
|
-
const result =
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
220
|
+
const result = schema.safeParse(
|
|
221
|
+
{
|
|
222
|
+
$type: 'blob',
|
|
223
|
+
ref: blobCid,
|
|
224
|
+
mimeType: 'image/jpeg',
|
|
225
|
+
size: 10000,
|
|
226
|
+
},
|
|
227
|
+
{ strict: true },
|
|
228
|
+
)
|
|
226
229
|
expect(result.success).toBe(true)
|
|
227
230
|
})
|
|
228
231
|
|
|
229
232
|
it('rejects non-raw CID in strict mode', () => {
|
|
230
|
-
const result =
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
const result = schema.safeParse(
|
|
234
|
+
{
|
|
235
|
+
$type: 'blob',
|
|
236
|
+
ref: lexCid,
|
|
237
|
+
mimeType: 'image/jpeg',
|
|
238
|
+
size: 10000,
|
|
239
|
+
},
|
|
240
|
+
{ strict: true },
|
|
241
|
+
)
|
|
236
242
|
expect(result.success).toBe(false)
|
|
237
243
|
})
|
|
238
244
|
|
|
239
245
|
it('accepts non-raw CID in non-strict mode', () => {
|
|
240
|
-
const
|
|
241
|
-
|
|
246
|
+
const result = schema.safeParse(
|
|
247
|
+
{
|
|
248
|
+
$type: 'blob',
|
|
249
|
+
ref: lexCid,
|
|
250
|
+
mimeType: 'image/jpeg',
|
|
251
|
+
size: 10000,
|
|
252
|
+
},
|
|
253
|
+
{ strict: false },
|
|
254
|
+
)
|
|
255
|
+
expect(result.success).toBe(true)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('coerces legacy blob format in non-strict parse mode', () => {
|
|
259
|
+
const result = schema.safeParse(
|
|
260
|
+
{
|
|
261
|
+
cid: lexCid.toString(),
|
|
262
|
+
mimeType: 'image/jpeg',
|
|
263
|
+
},
|
|
264
|
+
{ strict: false },
|
|
265
|
+
)
|
|
266
|
+
assert(result.success)
|
|
267
|
+
expect(result.value).toEqual({
|
|
242
268
|
$type: 'blob',
|
|
243
269
|
ref: lexCid,
|
|
244
270
|
mimeType: 'image/jpeg',
|
|
245
|
-
size:
|
|
271
|
+
size: -1,
|
|
246
272
|
})
|
|
247
|
-
expect(result.success).toBe(true)
|
|
248
273
|
})
|
|
249
274
|
})
|
|
250
275
|
|
|
@@ -455,51 +480,294 @@ describe('BlobSchema', () => {
|
|
|
455
480
|
})
|
|
456
481
|
})
|
|
457
482
|
|
|
458
|
-
describe('
|
|
459
|
-
|
|
460
|
-
const schema = blob(
|
|
483
|
+
describe('legacy blob format with strict mode combinations', () => {
|
|
484
|
+
describe('allowLegacy: false (default)', () => {
|
|
485
|
+
const schema = blob()
|
|
486
|
+
|
|
487
|
+
describe('strict: true (default)', () => {
|
|
488
|
+
it('rejects legacy blob format', () => {
|
|
489
|
+
const result = schema.safeParse({
|
|
490
|
+
cid: blobCid.toString(),
|
|
491
|
+
mimeType: 'image/jpeg',
|
|
492
|
+
})
|
|
493
|
+
expect(result.success).toBe(false)
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it('accepts standard BlobRef', () => {
|
|
497
|
+
const result = schema.safeParse({
|
|
498
|
+
$type: 'blob',
|
|
499
|
+
ref: blobCid,
|
|
500
|
+
mimeType: 'image/jpeg',
|
|
501
|
+
size: 10000,
|
|
502
|
+
})
|
|
503
|
+
expect(result.success).toBe(true)
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
describe('strict: false', () => {
|
|
508
|
+
it('coerces legacy blob format into BlobRef', () => {
|
|
509
|
+
const result = schema.safeParse(
|
|
510
|
+
{
|
|
511
|
+
cid: blobCid.toString(),
|
|
512
|
+
mimeType: 'image/jpeg',
|
|
513
|
+
},
|
|
514
|
+
{ strict: false },
|
|
515
|
+
)
|
|
516
|
+
assert(result.success)
|
|
517
|
+
expect(result.value).toEqual({
|
|
518
|
+
$type: 'blob',
|
|
519
|
+
ref: blobCid,
|
|
520
|
+
mimeType: 'image/jpeg',
|
|
521
|
+
size: -1,
|
|
522
|
+
})
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
it('coerces legacy blob format with lexCid', () => {
|
|
526
|
+
const result = schema.safeParse(
|
|
527
|
+
{
|
|
528
|
+
cid: lexCid.toString(),
|
|
529
|
+
mimeType: 'image/png',
|
|
530
|
+
},
|
|
531
|
+
{ strict: false },
|
|
532
|
+
)
|
|
533
|
+
assert(result.success)
|
|
534
|
+
expect(result.value).toEqual({
|
|
535
|
+
$type: 'blob',
|
|
536
|
+
ref: lexCid,
|
|
537
|
+
mimeType: 'image/png',
|
|
538
|
+
size: -1,
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
it('rejects legacy blob format with invalid cid', () => {
|
|
543
|
+
const result = schema.safeParse(
|
|
544
|
+
{
|
|
545
|
+
cid: 'invalid-cid',
|
|
546
|
+
mimeType: 'image/jpeg',
|
|
547
|
+
},
|
|
548
|
+
{ strict: false },
|
|
549
|
+
)
|
|
550
|
+
expect(result.success).toBe(false)
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
it('accepts standard BlobRef with non-raw CID', () => {
|
|
554
|
+
const result = schema.safeParse(
|
|
555
|
+
{
|
|
556
|
+
$type: 'blob',
|
|
557
|
+
ref: lexCid,
|
|
558
|
+
mimeType: 'image/jpeg',
|
|
559
|
+
size: 10000,
|
|
560
|
+
},
|
|
561
|
+
{ strict: false },
|
|
562
|
+
)
|
|
563
|
+
expect(result.success).toBe(true)
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
describe('allowLegacy: true', () => {
|
|
569
|
+
const schema = blob({ allowLegacy: true })
|
|
461
570
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
571
|
+
describe('strict: true (default)', () => {
|
|
572
|
+
it('accepts legacy blob format as LegacyBlobRef', () => {
|
|
573
|
+
const result = schema.safeParse({
|
|
574
|
+
cid: blobCid.toString(),
|
|
575
|
+
mimeType: 'image/jpeg',
|
|
576
|
+
})
|
|
577
|
+
assert(result.success)
|
|
578
|
+
expect('cid' in result.value && result.value.cid).toBe(
|
|
579
|
+
blobCid.toString(),
|
|
580
|
+
)
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
it('accepts standard BlobRef', () => {
|
|
584
|
+
const result = schema.safeParse({
|
|
585
|
+
$type: 'blob',
|
|
586
|
+
ref: blobCid,
|
|
587
|
+
mimeType: 'image/jpeg',
|
|
588
|
+
size: 10000,
|
|
589
|
+
})
|
|
590
|
+
expect(result.success).toBe(true)
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
it('rejects non-raw CID in BlobRef format (strict)', () => {
|
|
594
|
+
const result = schema.safeParse({
|
|
595
|
+
$type: 'blob',
|
|
596
|
+
ref: lexCid,
|
|
597
|
+
mimeType: 'image/jpeg',
|
|
598
|
+
size: 10000,
|
|
599
|
+
})
|
|
600
|
+
expect(result.success).toBe(false)
|
|
601
|
+
})
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
describe('strict: false', () => {
|
|
605
|
+
it('accepts legacy blob format as LegacyBlobRef', () => {
|
|
606
|
+
const result = schema.safeParse(
|
|
607
|
+
{
|
|
608
|
+
cid: blobCid.toString(),
|
|
609
|
+
mimeType: 'image/jpeg',
|
|
610
|
+
},
|
|
611
|
+
{ strict: false },
|
|
612
|
+
)
|
|
613
|
+
assert(result.success)
|
|
614
|
+
expect('cid' in result.value && result.value.cid).toBe(
|
|
615
|
+
blobCid.toString(),
|
|
616
|
+
)
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
it('accepts standard BlobRef with non-raw CID (non-strict)', () => {
|
|
620
|
+
const result = schema.safeParse(
|
|
621
|
+
{
|
|
622
|
+
$type: 'blob',
|
|
623
|
+
ref: lexCid,
|
|
624
|
+
mimeType: 'image/jpeg',
|
|
625
|
+
size: 10000,
|
|
626
|
+
},
|
|
627
|
+
{ strict: false },
|
|
628
|
+
)
|
|
629
|
+
expect(result.success).toBe(true)
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
it('accepts standard BlobRef with raw CID', () => {
|
|
633
|
+
const result = schema.safeParse(
|
|
634
|
+
{
|
|
635
|
+
$type: 'blob',
|
|
636
|
+
ref: blobCid,
|
|
637
|
+
mimeType: 'image/jpeg',
|
|
638
|
+
size: 10000,
|
|
639
|
+
},
|
|
640
|
+
{ strict: false },
|
|
641
|
+
)
|
|
642
|
+
expect(result.success).toBe(true)
|
|
643
|
+
})
|
|
468
644
|
})
|
|
469
|
-
|
|
645
|
+
})
|
|
646
|
+
})
|
|
470
647
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
648
|
+
describe('mime and size checks depend on strict mode', () => {
|
|
649
|
+
describe('accept constraint', () => {
|
|
650
|
+
const schema = blob({ accept: ['image/jpeg', 'image/png'] })
|
|
651
|
+
|
|
652
|
+
it('rejects non-matching mime type in strict mode (default)', () => {
|
|
653
|
+
const result = schema.safeParse({
|
|
654
|
+
$type: 'blob',
|
|
655
|
+
ref: blobCid,
|
|
656
|
+
mimeType: 'image/gif',
|
|
657
|
+
size: 10000,
|
|
658
|
+
})
|
|
659
|
+
expect(result.success).toBe(false)
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
it('accepts non-matching mime type in non-strict mode', () => {
|
|
663
|
+
const result = schema.safeParse(
|
|
664
|
+
{
|
|
665
|
+
$type: 'blob',
|
|
666
|
+
ref: blobCid,
|
|
667
|
+
mimeType: 'image/gif',
|
|
668
|
+
size: 10000,
|
|
669
|
+
},
|
|
670
|
+
{ strict: false },
|
|
671
|
+
)
|
|
672
|
+
expect(result.success).toBe(true)
|
|
475
673
|
})
|
|
476
|
-
expect(legacyResult.success).toBe(true)
|
|
477
674
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
675
|
+
it('accepts matching mime type in strict mode', () => {
|
|
676
|
+
const result = schema.safeParse({
|
|
677
|
+
$type: 'blob',
|
|
678
|
+
ref: blobCid,
|
|
679
|
+
mimeType: 'image/jpeg',
|
|
680
|
+
size: 10000,
|
|
681
|
+
})
|
|
682
|
+
expect(result.success).toBe(true)
|
|
484
683
|
})
|
|
485
|
-
expect(nonRawResult.success).toBe(false)
|
|
486
684
|
})
|
|
487
685
|
|
|
488
|
-
|
|
686
|
+
describe('maxSize constraint', () => {
|
|
687
|
+
const schema = blob({ maxSize: 1000 })
|
|
688
|
+
|
|
689
|
+
it('rejects oversized blob in strict mode (default)', () => {
|
|
690
|
+
const result = schema.safeParse({
|
|
691
|
+
$type: 'blob',
|
|
692
|
+
ref: blobCid,
|
|
693
|
+
mimeType: 'image/jpeg',
|
|
694
|
+
size: 5000,
|
|
695
|
+
})
|
|
696
|
+
expect(result.success).toBe(false)
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
it('accepts oversized blob in non-strict mode', () => {
|
|
700
|
+
const result = schema.safeParse(
|
|
701
|
+
{
|
|
702
|
+
$type: 'blob',
|
|
703
|
+
ref: blobCid,
|
|
704
|
+
mimeType: 'image/jpeg',
|
|
705
|
+
size: 5000,
|
|
706
|
+
},
|
|
707
|
+
{ strict: false },
|
|
708
|
+
)
|
|
709
|
+
expect(result.success).toBe(true)
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
it('accepts correctly sized blob in strict mode', () => {
|
|
713
|
+
const result = schema.safeParse({
|
|
714
|
+
$type: 'blob',
|
|
715
|
+
ref: blobCid,
|
|
716
|
+
mimeType: 'image/jpeg',
|
|
717
|
+
size: 500,
|
|
718
|
+
})
|
|
719
|
+
expect(result.success).toBe(true)
|
|
720
|
+
})
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
describe('combined accept and maxSize constraints', () => {
|
|
489
724
|
const schema = blob({
|
|
490
|
-
strict: true,
|
|
491
|
-
allowLegacy: true,
|
|
492
725
|
accept: ['image/jpeg'],
|
|
493
726
|
maxSize: 20000,
|
|
494
727
|
})
|
|
495
728
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
729
|
+
it('accepts valid blob in strict mode', () => {
|
|
730
|
+
const result = schema.safeParse({
|
|
731
|
+
$type: 'blob',
|
|
732
|
+
ref: blobCid,
|
|
733
|
+
mimeType: 'image/jpeg',
|
|
734
|
+
size: 10000,
|
|
735
|
+
})
|
|
736
|
+
expect(result.success).toBe(true)
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
it('rejects wrong mime in strict mode', () => {
|
|
740
|
+
const result = schema.safeParse({
|
|
741
|
+
$type: 'blob',
|
|
742
|
+
ref: blobCid,
|
|
743
|
+
mimeType: 'image/png',
|
|
744
|
+
size: 10000,
|
|
745
|
+
})
|
|
746
|
+
expect(result.success).toBe(false)
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
it('rejects oversized in strict mode', () => {
|
|
750
|
+
const result = schema.safeParse({
|
|
751
|
+
$type: 'blob',
|
|
752
|
+
ref: blobCid,
|
|
753
|
+
mimeType: 'image/jpeg',
|
|
754
|
+
size: 30000,
|
|
755
|
+
})
|
|
756
|
+
expect(result.success).toBe(false)
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
it('accepts wrong mime and oversized in non-strict mode', () => {
|
|
760
|
+
const result = schema.safeParse(
|
|
761
|
+
{
|
|
762
|
+
$type: 'blob',
|
|
763
|
+
ref: blobCid,
|
|
764
|
+
mimeType: 'video/mp4',
|
|
765
|
+
size: 99999,
|
|
766
|
+
},
|
|
767
|
+
{ strict: false },
|
|
768
|
+
)
|
|
769
|
+
expect(result.success).toBe(true)
|
|
501
770
|
})
|
|
502
|
-
expect(result.success).toBe(true)
|
|
503
771
|
})
|
|
504
772
|
})
|
|
505
773
|
})
|