@atproto/lex-schema 0.0.15 → 0.0.17
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 +28 -0
- package/dist/core/schema.d.ts +6 -12
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +11 -17
- package/dist/core/schema.js.map +1 -1
- package/dist/core/string-format.d.ts +22 -4
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +43 -19
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validation-error.d.ts +2 -2
- package/dist/core/validation-error.d.ts.map +1 -1
- package/dist/core/validation-error.js +1 -1
- package/dist/core/validation-error.js.map +1 -1
- package/dist/core/validation-issue.d.ts.map +1 -1
- package/dist/core/validation-issue.js +19 -38
- package/dist/core/validation-issue.js.map +1 -1
- package/dist/core/validator.d.ts +13 -1
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +1 -0
- package/dist/core/validator.js.map +1 -1
- package/dist/helpers.d.ts +4 -3
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +6 -1
- package/dist/helpers.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/payload.d.ts +2 -2
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js +2 -3
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/record.d.ts +6 -8
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +1 -1
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/regexp.d.ts +3 -2
- package/dist/schema/regexp.d.ts.map +1 -1
- package/dist/schema/regexp.js +6 -4
- package/dist/schema/regexp.js.map +1 -1
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +10 -3
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/typed-object.d.ts +5 -7
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +1 -1
- package/dist/schema/typed-object.js.map +1 -1
- package/package.json +4 -3
- package/src/core/$type.test.ts +9 -5
- package/src/core/schema.ts +20 -17
- package/src/core/string-format.ts +62 -16
- package/src/core/validation-error.ts +2 -2
- package/src/core/validation-issue.ts +20 -36
- package/src/core/validator.ts +17 -1
- package/src/helpers.ts +7 -1
- package/src/schema/array.test.ts +1 -1
- package/src/schema/blob.test.ts +317 -49
- package/src/schema/blob.ts +56 -23
- package/src/schema/params.test.ts +2 -2
- package/src/schema/payload.ts +4 -5
- package/src/schema/record.test.ts +135 -17
- package/src/schema/record.ts +14 -9
- package/src/schema/regexp.ts +14 -4
- package/src/schema/string.test.ts +63 -0
- package/src/schema/string.ts +9 -3
- package/src/schema/typed-object.test.ts +77 -0
- package/src/schema/typed-object.ts +11 -10
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
|
})
|
package/src/schema/blob.ts
CHANGED
|
@@ -1,32 +1,36 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BlobRef,
|
|
3
|
-
BlobRefCheckOptions,
|
|
4
3
|
LegacyBlobRef,
|
|
5
4
|
isBlobRef,
|
|
6
5
|
isLegacyBlobRef,
|
|
6
|
+
parseCidSafe,
|
|
7
7
|
} from '@atproto/lex-data'
|
|
8
8
|
import { Schema, ValidationContext } from '../core.js'
|
|
9
9
|
import { memoizedOptions } from '../util/memoize.js'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Configuration options for blob schema validation.
|
|
13
|
-
*
|
|
14
|
-
* @property allowLegacy - Whether to allow legacy blob references format
|
|
15
|
-
* @property accept - List of accepted MIME types (supports wildcards like 'image/*' or '*\/*')
|
|
16
|
-
* @property maxSize - Maximum blob size in bytes
|
|
17
13
|
*/
|
|
18
|
-
export type BlobSchemaOptions =
|
|
14
|
+
export type BlobSchemaOptions = {
|
|
19
15
|
/**
|
|
20
16
|
* Whether to allow legacy blob references format
|
|
17
|
+
*
|
|
18
|
+
* @default false
|
|
21
19
|
* @see {@link LegacyBlobRef}
|
|
22
20
|
*/
|
|
23
21
|
allowLegacy?: boolean
|
|
22
|
+
|
|
24
23
|
/**
|
|
25
|
-
* List of accepted
|
|
24
|
+
* List of accepted MIME types (supports wildcards like 'image/*' or '*\/*')
|
|
25
|
+
*
|
|
26
|
+
* @default undefined // accepts all MIME types
|
|
26
27
|
*/
|
|
27
28
|
accept?: string[]
|
|
29
|
+
|
|
28
30
|
/**
|
|
29
|
-
* Maximum size in bytes
|
|
31
|
+
* Maximum blob size in bytes
|
|
32
|
+
*
|
|
33
|
+
* @default undefined // no size limit
|
|
30
34
|
*/
|
|
31
35
|
maxSize?: number
|
|
32
36
|
}
|
|
@@ -61,27 +65,24 @@ export class BlobSchema<
|
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
validateInContext(input: unknown, ctx: ValidationContext) {
|
|
64
|
-
const blob
|
|
65
|
-
(input as any)?.$type !== undefined
|
|
66
|
-
? isBlobRef(input, this.options)
|
|
67
|
-
? input
|
|
68
|
-
: null
|
|
69
|
-
: this.options?.allowLegacy === true && isLegacyBlobRef(input)
|
|
70
|
-
? input
|
|
71
|
-
: null
|
|
68
|
+
const blob = parseValue.call(ctx, input, this.options)
|
|
72
69
|
|
|
73
70
|
if (!blob) {
|
|
74
71
|
return ctx.issueUnexpectedType(input, 'blob')
|
|
75
72
|
}
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
// In non-strict mode, we allow blob refs to pass through without MIME
|
|
75
|
+
// type or size checks.
|
|
76
|
+
if (ctx.options.strict) {
|
|
77
|
+
const accept = this.options?.accept
|
|
78
|
+
if (accept && !matchesMime(blob.mimeType, accept)) {
|
|
79
|
+
return ctx.issueInvalidPropertyValue(blob, 'mimeType', accept)
|
|
80
|
+
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const maxSize = this.options?.maxSize
|
|
83
|
+
if (maxSize != null && 'size' in blob && blob.size > maxSize) {
|
|
84
|
+
return ctx.issueTooBig(blob, 'blob', maxSize, blob.size)
|
|
85
|
+
}
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
return ctx.success(blob)
|
|
@@ -94,6 +95,38 @@ export class BlobSchema<
|
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
function parseValue(
|
|
99
|
+
this: ValidationContext,
|
|
100
|
+
input: unknown,
|
|
101
|
+
options?: BlobSchemaOptions,
|
|
102
|
+
): BlobRef | LegacyBlobRef | null {
|
|
103
|
+
// If there is a $type property, we treat if as a potential BlobRef and
|
|
104
|
+
// validate accordingly.
|
|
105
|
+
if ((input as any)?.$type !== undefined) {
|
|
106
|
+
// Use the context's option for the "strict" check
|
|
107
|
+
return isBlobRef(input, this.options) ? input : null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// If there is no $type property, we may be dealing with a legacy blob ref. If
|
|
111
|
+
// legacy refs are allowed, validate against the legacy format. If not
|
|
112
|
+
// allowed, but we are in non-strict "parse" mode, coerce legacy refs into
|
|
113
|
+
// standard BlobRef format for backward compatibility. Otherwise, reject the
|
|
114
|
+
// value.
|
|
115
|
+
if (options?.allowLegacy) {
|
|
116
|
+
if (isLegacyBlobRef(input)) {
|
|
117
|
+
return input
|
|
118
|
+
}
|
|
119
|
+
} else if (!this.options.strict && this.options.mode === 'parse') {
|
|
120
|
+
if (isLegacyBlobRef(input)) {
|
|
121
|
+
const { cid, mimeType } = input
|
|
122
|
+
const ref = parseCidSafe(cid)
|
|
123
|
+
if (ref) return { $type: 'blob', ref, mimeType, size: -1 }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
97
130
|
function matchesMime(mime: string, accepted: string[]): boolean {
|
|
98
131
|
if (accepted.includes('*/*')) return true
|
|
99
132
|
if (accepted.includes(mime)) return true
|
|
@@ -419,7 +419,7 @@ describe('ParamsSchema', () => {
|
|
|
419
419
|
['name', 'Alice'],
|
|
420
420
|
['bools', 'notabool'],
|
|
421
421
|
]),
|
|
422
|
-
).toThrow('Expected boolean value type (got
|
|
422
|
+
).toThrow('Expected boolean value type (got "notabool") at $.bools')
|
|
423
423
|
|
|
424
424
|
expect(() =>
|
|
425
425
|
schema.fromURLSearchParams(
|
|
@@ -431,7 +431,7 @@ describe('ParamsSchema', () => {
|
|
|
431
431
|
path: ['foo', 'bar'],
|
|
432
432
|
},
|
|
433
433
|
),
|
|
434
|
-
).toThrow('Expected boolean value type (got
|
|
434
|
+
).toThrow('Expected boolean value type (got "2") at $.foo.bar.bools')
|
|
435
435
|
})
|
|
436
436
|
|
|
437
437
|
it('ignores empty string values', () => {
|
package/src/schema/payload.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LexValue } from '@atproto/lex-data'
|
|
2
|
-
import {
|
|
2
|
+
import { InferInput, Schema, Validator } from '../core.js'
|
|
3
3
|
import { ObjectSchema, object } from './object.js'
|
|
4
4
|
|
|
5
5
|
export type { LexValue }
|
|
@@ -15,7 +15,7 @@ type ToBodyType<
|
|
|
15
15
|
TSchema,
|
|
16
16
|
TBinary,
|
|
17
17
|
> = TSchema extends Schema
|
|
18
|
-
?
|
|
18
|
+
? InferInput<TSchema>
|
|
19
19
|
: TEncoding extends `application/json`
|
|
20
20
|
? LexValue
|
|
21
21
|
: TBinary
|
|
@@ -109,10 +109,9 @@ export class Payload<
|
|
|
109
109
|
matchesEncoding(contentType: string | undefined): boolean {
|
|
110
110
|
const { encoding } = this
|
|
111
111
|
|
|
112
|
-
// Handle undefined cases
|
|
113
112
|
if (encoding === undefined) {
|
|
114
|
-
//
|
|
115
|
-
return
|
|
113
|
+
// When the output is not defined, we don't enforce any rule on the payload.
|
|
114
|
+
return true
|
|
116
115
|
} else if (contentType == null) {
|
|
117
116
|
// Expecting a body, but got no content-type
|
|
118
117
|
return false
|