+504
-2
Cargo.lock
+504
-2
Cargo.lock
···
102
102
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
103
103
104
104
[[package]]
105
+
name = "base64"
106
+
version = "0.22.1"
107
+
source = "registry+https://github.com/rust-lang/crates.io-index"
108
+
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
109
+
110
+
[[package]]
105
111
name = "block-buffer"
106
112
version = "0.10.4"
107
113
source = "registry+https://github.com/rust-lang/crates.io-index"
···
150
156
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
151
157
152
158
[[package]]
159
+
name = "bytes"
160
+
version = "1.10.1"
161
+
source = "registry+https://github.com/rust-lang/crates.io-index"
162
+
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
163
+
164
+
[[package]]
153
165
name = "cc"
154
166
version = "1.2.39"
155
167
source = "registry+https://github.com/rust-lang/crates.io-index"
···
189
201
"iana-time-zone",
190
202
"js-sys",
191
203
"num-traits",
204
+
"serde",
192
205
"wasm-bindgen",
193
206
"windows-link",
194
207
]
···
321
334
]
322
335
323
336
[[package]]
337
+
name = "darling"
338
+
version = "0.21.3"
339
+
source = "registry+https://github.com/rust-lang/crates.io-index"
340
+
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
341
+
dependencies = [
342
+
"darling_core",
343
+
"darling_macro",
344
+
]
345
+
346
+
[[package]]
347
+
name = "darling_core"
348
+
version = "0.21.3"
349
+
source = "registry+https://github.com/rust-lang/crates.io-index"
350
+
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
351
+
dependencies = [
352
+
"fnv",
353
+
"ident_case",
354
+
"proc-macro2",
355
+
"quote",
356
+
"strsim",
357
+
"syn 2.0.106",
358
+
]
359
+
360
+
[[package]]
361
+
name = "darling_macro"
362
+
version = "0.21.3"
363
+
source = "registry+https://github.com/rust-lang/crates.io-index"
364
+
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
365
+
dependencies = [
366
+
"darling_core",
367
+
"quote",
368
+
"syn 2.0.106",
369
+
]
370
+
371
+
[[package]]
324
372
name = "data-encoding"
325
373
version = "2.9.0"
326
374
source = "registry+https://github.com/rust-lang/crates.io-index"
···
347
395
]
348
396
349
397
[[package]]
398
+
name = "deranged"
399
+
version = "0.5.4"
400
+
source = "registry+https://github.com/rust-lang/crates.io-index"
401
+
checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071"
402
+
dependencies = [
403
+
"powerfmt",
404
+
"serde_core",
405
+
]
406
+
407
+
[[package]]
350
408
name = "digest"
351
409
version = "0.10.7"
352
410
source = "registry+https://github.com/rust-lang/crates.io-index"
···
357
415
]
358
416
359
417
[[package]]
418
+
name = "displaydoc"
419
+
version = "0.2.5"
420
+
source = "registry+https://github.com/rust-lang/crates.io-index"
421
+
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
422
+
dependencies = [
423
+
"proc-macro2",
424
+
"quote",
425
+
"syn 2.0.106",
426
+
]
427
+
428
+
[[package]]
429
+
name = "dyn-clone"
430
+
version = "1.0.20"
431
+
source = "registry+https://github.com/rust-lang/crates.io-index"
432
+
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
433
+
434
+
[[package]]
435
+
name = "enum_dispatch"
436
+
version = "0.3.13"
437
+
source = "registry+https://github.com/rust-lang/crates.io-index"
438
+
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
439
+
dependencies = [
440
+
"once_cell",
441
+
"proc-macro2",
442
+
"quote",
443
+
"syn 2.0.106",
444
+
]
445
+
446
+
[[package]]
360
447
name = "equivalent"
361
448
version = "1.0.2"
362
449
source = "registry+https://github.com/rust-lang/crates.io-index"
···
367
454
version = "0.1.2"
368
455
source = "registry+https://github.com/rust-lang/crates.io-index"
369
456
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
457
+
458
+
[[package]]
459
+
name = "fnv"
460
+
version = "1.0.7"
461
+
source = "registry+https://github.com/rust-lang/crates.io-index"
462
+
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
370
463
371
464
[[package]]
372
465
name = "form_urlencoded"
···
399
492
400
493
[[package]]
401
494
name = "hashbrown"
495
+
version = "0.12.3"
496
+
source = "registry+https://github.com/rust-lang/crates.io-index"
497
+
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
498
+
499
+
[[package]]
500
+
name = "hashbrown"
402
501
version = "0.16.0"
403
502
source = "registry+https://github.com/rust-lang/crates.io-index"
404
503
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
···
408
507
version = "0.5.0"
409
508
source = "registry+https://github.com/rust-lang/crates.io-index"
410
509
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
510
+
511
+
[[package]]
512
+
name = "hex"
513
+
version = "0.4.3"
514
+
source = "registry+https://github.com/rust-lang/crates.io-index"
515
+
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
411
516
412
517
[[package]]
413
518
name = "hex_fmt"
···
440
545
]
441
546
442
547
[[package]]
548
+
name = "icu_collections"
549
+
version = "2.0.0"
550
+
source = "registry+https://github.com/rust-lang/crates.io-index"
551
+
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
552
+
dependencies = [
553
+
"displaydoc",
554
+
"potential_utf",
555
+
"yoke",
556
+
"zerofrom",
557
+
"zerovec",
558
+
]
559
+
560
+
[[package]]
561
+
name = "icu_locale_core"
562
+
version = "2.0.0"
563
+
source = "registry+https://github.com/rust-lang/crates.io-index"
564
+
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
565
+
dependencies = [
566
+
"displaydoc",
567
+
"litemap",
568
+
"tinystr",
569
+
"writeable",
570
+
"zerovec",
571
+
]
572
+
573
+
[[package]]
574
+
name = "icu_normalizer"
575
+
version = "2.0.0"
576
+
source = "registry+https://github.com/rust-lang/crates.io-index"
577
+
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
578
+
dependencies = [
579
+
"displaydoc",
580
+
"icu_collections",
581
+
"icu_normalizer_data",
582
+
"icu_properties",
583
+
"icu_provider",
584
+
"smallvec",
585
+
"zerovec",
586
+
]
587
+
588
+
[[package]]
589
+
name = "icu_normalizer_data"
590
+
version = "2.0.0"
591
+
source = "registry+https://github.com/rust-lang/crates.io-index"
592
+
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
593
+
594
+
[[package]]
595
+
name = "icu_properties"
596
+
version = "2.0.1"
597
+
source = "registry+https://github.com/rust-lang/crates.io-index"
598
+
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
599
+
dependencies = [
600
+
"displaydoc",
601
+
"icu_collections",
602
+
"icu_locale_core",
603
+
"icu_properties_data",
604
+
"icu_provider",
605
+
"potential_utf",
606
+
"zerotrie",
607
+
"zerovec",
608
+
]
609
+
610
+
[[package]]
611
+
name = "icu_properties_data"
612
+
version = "2.0.1"
613
+
source = "registry+https://github.com/rust-lang/crates.io-index"
614
+
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
615
+
616
+
[[package]]
617
+
name = "icu_provider"
618
+
version = "2.0.0"
619
+
source = "registry+https://github.com/rust-lang/crates.io-index"
620
+
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
621
+
dependencies = [
622
+
"displaydoc",
623
+
"icu_locale_core",
624
+
"stable_deref_trait",
625
+
"tinystr",
626
+
"writeable",
627
+
"yoke",
628
+
"zerofrom",
629
+
"zerotrie",
630
+
"zerovec",
631
+
]
632
+
633
+
[[package]]
634
+
name = "ident_case"
635
+
version = "1.0.1"
636
+
source = "registry+https://github.com/rust-lang/crates.io-index"
637
+
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
638
+
639
+
[[package]]
640
+
name = "idna"
641
+
version = "1.1.0"
642
+
source = "registry+https://github.com/rust-lang/crates.io-index"
643
+
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
644
+
dependencies = [
645
+
"idna_adapter",
646
+
"smallvec",
647
+
"utf8_iter",
648
+
]
649
+
650
+
[[package]]
651
+
name = "idna_adapter"
652
+
version = "1.2.1"
653
+
source = "registry+https://github.com/rust-lang/crates.io-index"
654
+
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
655
+
dependencies = [
656
+
"icu_normalizer",
657
+
"icu_properties",
658
+
]
659
+
660
+
[[package]]
661
+
name = "indexmap"
662
+
version = "1.9.3"
663
+
source = "registry+https://github.com/rust-lang/crates.io-index"
664
+
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
665
+
dependencies = [
666
+
"autocfg",
667
+
"hashbrown 0.12.3",
668
+
"serde",
669
+
]
670
+
671
+
[[package]]
443
672
name = "indexmap"
444
673
version = "2.11.4"
445
674
source = "registry+https://github.com/rust-lang/crates.io-index"
446
675
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
447
676
dependencies = [
448
677
"equivalent",
449
-
"hashbrown",
678
+
"hashbrown 0.16.0",
679
+
"serde",
680
+
"serde_core",
450
681
]
451
682
452
683
[[package]]
···
479
710
name = "jacquard-common"
480
711
version = "0.1.0"
481
712
dependencies = [
713
+
"bytes",
482
714
"chrono",
483
715
"cid",
716
+
"enum_dispatch",
484
717
"langtag",
485
718
"miette",
486
719
"multibase",
···
489
722
"serde",
490
723
"serde_html_form",
491
724
"serde_json",
725
+
"serde_with",
492
726
"smol_str",
493
727
"thiserror 2.0.16",
728
+
"url",
494
729
]
495
730
496
731
[[package]]
732
+
name = "jacquard-lexicon"
733
+
version = "0.1.0"
734
+
735
+
[[package]]
497
736
name = "js-sys"
498
737
version = "0.3.81"
499
738
source = "registry+https://github.com/rust-lang/crates.io-index"
···
519
758
version = "0.2.176"
520
759
source = "registry+https://github.com/rust-lang/crates.io-index"
521
760
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
761
+
762
+
[[package]]
763
+
name = "litemap"
764
+
version = "0.8.0"
765
+
source = "registry+https://github.com/rust-lang/crates.io-index"
766
+
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
522
767
523
768
[[package]]
524
769
name = "log"
···
591
836
"memchr",
592
837
"minimal-lexical",
593
838
]
839
+
840
+
[[package]]
841
+
name = "num-conv"
842
+
version = "0.1.0"
843
+
source = "registry+https://github.com/rust-lang/crates.io-index"
844
+
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
594
845
595
846
[[package]]
596
847
name = "num-traits"
···
620
871
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
621
872
622
873
[[package]]
874
+
name = "potential_utf"
875
+
version = "0.1.3"
876
+
source = "registry+https://github.com/rust-lang/crates.io-index"
877
+
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
878
+
dependencies = [
879
+
"zerovec",
880
+
]
881
+
882
+
[[package]]
883
+
name = "powerfmt"
884
+
version = "0.2.0"
885
+
source = "registry+https://github.com/rust-lang/crates.io-index"
886
+
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
887
+
888
+
[[package]]
623
889
name = "proc-macro-error"
624
890
version = "1.0.4"
625
891
source = "registry+https://github.com/rust-lang/crates.io-index"
···
668
934
checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab"
669
935
670
936
[[package]]
937
+
name = "ref-cast"
938
+
version = "1.0.24"
939
+
source = "registry+https://github.com/rust-lang/crates.io-index"
940
+
checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
941
+
dependencies = [
942
+
"ref-cast-impl",
943
+
]
944
+
945
+
[[package]]
946
+
name = "ref-cast-impl"
947
+
version = "1.0.24"
948
+
source = "registry+https://github.com/rust-lang/crates.io-index"
949
+
checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
950
+
dependencies = [
951
+
"proc-macro2",
952
+
"quote",
953
+
"syn 2.0.106",
954
+
]
955
+
956
+
[[package]]
671
957
name = "regex"
672
958
version = "1.11.3"
673
959
source = "registry+https://github.com/rust-lang/crates.io-index"
···
709
995
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
710
996
711
997
[[package]]
998
+
name = "schemars"
999
+
version = "0.9.0"
1000
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1001
+
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
1002
+
dependencies = [
1003
+
"dyn-clone",
1004
+
"ref-cast",
1005
+
"serde",
1006
+
"serde_json",
1007
+
]
1008
+
1009
+
[[package]]
1010
+
name = "schemars"
1011
+
version = "1.0.4"
1012
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1013
+
checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
1014
+
dependencies = [
1015
+
"dyn-clone",
1016
+
"ref-cast",
1017
+
"serde",
1018
+
"serde_json",
1019
+
]
1020
+
1021
+
[[package]]
712
1022
name = "serde"
713
1023
version = "1.0.227"
714
1024
source = "registry+https://github.com/rust-lang/crates.io-index"
···
754
1064
checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
755
1065
dependencies = [
756
1066
"form_urlencoded",
757
-
"indexmap",
1067
+
"indexmap 2.11.4",
758
1068
"itoa",
759
1069
"ryu",
760
1070
"serde_core",
···
774
1084
]
775
1085
776
1086
[[package]]
1087
+
name = "serde_with"
1088
+
version = "3.14.1"
1089
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1090
+
checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e"
1091
+
dependencies = [
1092
+
"base64",
1093
+
"chrono",
1094
+
"hex",
1095
+
"indexmap 1.9.3",
1096
+
"indexmap 2.11.4",
1097
+
"schemars 0.9.0",
1098
+
"schemars 1.0.4",
1099
+
"serde",
1100
+
"serde_derive",
1101
+
"serde_json",
1102
+
"serde_with_macros",
1103
+
"time",
1104
+
]
1105
+
1106
+
[[package]]
1107
+
name = "serde_with_macros"
1108
+
version = "3.14.1"
1109
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1110
+
checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e"
1111
+
dependencies = [
1112
+
"darling",
1113
+
"proc-macro2",
1114
+
"quote",
1115
+
"syn 2.0.106",
1116
+
]
1117
+
1118
+
[[package]]
777
1119
name = "sha2"
778
1120
version = "0.10.9"
779
1121
source = "registry+https://github.com/rust-lang/crates.io-index"
···
813
1155
]
814
1156
815
1157
[[package]]
1158
+
name = "stable_deref_trait"
1159
+
version = "1.2.0"
1160
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1161
+
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
1162
+
1163
+
[[package]]
816
1164
name = "static-regular-grammar"
817
1165
version = "2.0.2"
818
1166
source = "registry+https://github.com/rust-lang/crates.io-index"
···
860
1208
]
861
1209
862
1210
[[package]]
1211
+
name = "synstructure"
1212
+
version = "0.13.2"
1213
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1214
+
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
1215
+
dependencies = [
1216
+
"proc-macro2",
1217
+
"quote",
1218
+
"syn 2.0.106",
1219
+
]
1220
+
1221
+
[[package]]
863
1222
name = "thiserror"
864
1223
version = "1.0.69"
865
1224
source = "registry+https://github.com/rust-lang/crates.io-index"
···
900
1259
]
901
1260
902
1261
[[package]]
1262
+
name = "time"
1263
+
version = "0.3.44"
1264
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1265
+
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
1266
+
dependencies = [
1267
+
"deranged",
1268
+
"itoa",
1269
+
"num-conv",
1270
+
"powerfmt",
1271
+
"serde",
1272
+
"time-core",
1273
+
"time-macros",
1274
+
]
1275
+
1276
+
[[package]]
1277
+
name = "time-core"
1278
+
version = "0.1.6"
1279
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1280
+
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
1281
+
1282
+
[[package]]
1283
+
name = "time-macros"
1284
+
version = "0.2.24"
1285
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1286
+
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
1287
+
dependencies = [
1288
+
"num-conv",
1289
+
"time-core",
1290
+
]
1291
+
1292
+
[[package]]
1293
+
name = "tinystr"
1294
+
version = "0.8.1"
1295
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1296
+
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
1297
+
dependencies = [
1298
+
"displaydoc",
1299
+
"zerovec",
1300
+
]
1301
+
1302
+
[[package]]
903
1303
name = "typenum"
904
1304
version = "1.18.0"
905
1305
source = "registry+https://github.com/rust-lang/crates.io-index"
···
922
1322
version = "0.8.0"
923
1323
source = "registry+https://github.com/rust-lang/crates.io-index"
924
1324
checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06"
1325
+
1326
+
[[package]]
1327
+
name = "url"
1328
+
version = "2.5.7"
1329
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1330
+
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
1331
+
dependencies = [
1332
+
"form_urlencoded",
1333
+
"idna",
1334
+
"percent-encoding",
1335
+
"serde",
1336
+
]
1337
+
1338
+
[[package]]
1339
+
name = "utf8_iter"
1340
+
version = "1.0.4"
1341
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1342
+
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
925
1343
926
1344
[[package]]
927
1345
name = "utf8parse"
···
1126
1544
version = "0.53.0"
1127
1545
source = "registry+https://github.com/rust-lang/crates.io-index"
1128
1546
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
1547
+
1548
+
[[package]]
1549
+
name = "writeable"
1550
+
version = "0.6.1"
1551
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1552
+
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
1553
+
1554
+
[[package]]
1555
+
name = "yoke"
1556
+
version = "0.8.0"
1557
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1558
+
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
1559
+
dependencies = [
1560
+
"serde",
1561
+
"stable_deref_trait",
1562
+
"yoke-derive",
1563
+
"zerofrom",
1564
+
]
1565
+
1566
+
[[package]]
1567
+
name = "yoke-derive"
1568
+
version = "0.8.0"
1569
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1570
+
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
1571
+
dependencies = [
1572
+
"proc-macro2",
1573
+
"quote",
1574
+
"syn 2.0.106",
1575
+
"synstructure",
1576
+
]
1577
+
1578
+
[[package]]
1579
+
name = "zerofrom"
1580
+
version = "0.1.6"
1581
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1582
+
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
1583
+
dependencies = [
1584
+
"zerofrom-derive",
1585
+
]
1586
+
1587
+
[[package]]
1588
+
name = "zerofrom-derive"
1589
+
version = "0.1.6"
1590
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1591
+
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
1592
+
dependencies = [
1593
+
"proc-macro2",
1594
+
"quote",
1595
+
"syn 2.0.106",
1596
+
"synstructure",
1597
+
]
1598
+
1599
+
[[package]]
1600
+
name = "zerotrie"
1601
+
version = "0.2.2"
1602
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1603
+
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
1604
+
dependencies = [
1605
+
"displaydoc",
1606
+
"yoke",
1607
+
"zerofrom",
1608
+
]
1609
+
1610
+
[[package]]
1611
+
name = "zerovec"
1612
+
version = "0.11.4"
1613
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1614
+
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
1615
+
dependencies = [
1616
+
"yoke",
1617
+
"zerofrom",
1618
+
"zerovec-derive",
1619
+
]
1620
+
1621
+
[[package]]
1622
+
name = "zerovec-derive"
1623
+
version = "0.11.1"
1624
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1625
+
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
1626
+
dependencies = [
1627
+
"proc-macro2",
1628
+
"quote",
1629
+
"syn 2.0.106",
1630
+
]
+10
crates/jacquard-common/Cargo.toml
+10
crates/jacquard-common/Cargo.toml
···
3
3
edition.workspace = true
4
4
version.workspace = true
5
5
authors.workspace = true
6
+
repository.workspace = true
7
+
keywords.workspace = true
8
+
categories.workspace = true
9
+
readme.workspace = true
10
+
documentation.workspace = true
11
+
exclude.workspace = true
6
12
description.workspace = true
7
13
8
14
[dependencies]
15
+
bytes = "1.10.1"
9
16
chrono = "0.4.42"
10
17
cid = { version = "0.11.1", features = ["serde", "std"] }
18
+
enum_dispatch = "0.3.13"
11
19
langtag = { version = "0.4.0", features = ["serde"] }
12
20
miette = "7.6.0"
13
21
multibase = "0.9.1"
···
16
24
serde = { version = "1.0.227", features = ["derive"] }
17
25
serde_html_form = "0.2.8"
18
26
serde_json = "1.0.145"
27
+
serde_with = "3.14.1"
19
28
smol_str = { version = "0.3.2", features = ["serde"] }
20
29
thiserror = "2.0.16"
30
+
url = "2.5.7"
+3
crates/jacquard-common/src/lib.rs
+3
crates/jacquard-common/src/lib.rs
+90
crates/jacquard-common/src/types.rs
+90
crates/jacquard-common/src/types.rs
···
1
+
use serde::{Deserialize, Serialize};
2
+
1
3
pub mod aturi;
2
4
pub mod blob;
3
5
pub mod cid;
···
11
13
pub mod link;
12
14
pub mod nsid;
13
15
pub mod recordkey;
16
+
pub mod string;
14
17
pub mod tid;
18
+
pub mod uri;
19
+
pub mod value;
15
20
16
21
/// Trait for a constant string literal type
17
22
pub trait Literal: Clone + Copy + PartialEq + Eq + Send + Sync + 'static {
18
23
/// The string literal
19
24
const LITERAL: &'static str;
20
25
}
26
+
27
+
pub const DISALLOWED_TLDS: &[&str] = &[
28
+
".local",
29
+
".arpa",
30
+
".invalid",
31
+
".localhost",
32
+
".internal",
33
+
".example",
34
+
".alt",
35
+
// policy could concievably change on ".onion" some day
36
+
".onion",
37
+
// NOTE: .test is allowed in testing and devopment. In practical terms
38
+
// "should" "never" actually resolve and get registered in production
39
+
];
40
+
41
+
pub fn ends_with(string: impl AsRef<str>, list: &[&str]) -> bool {
42
+
let string = string.as_ref();
43
+
for item in list {
44
+
if string.ends_with(item) {
45
+
return true;
46
+
}
47
+
}
48
+
false
49
+
}
50
+
51
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
52
+
#[serde(rename_all = "kebab-case")]
53
+
pub enum DataModelType {
54
+
Null,
55
+
Boolean,
56
+
Integer,
57
+
Bytes,
58
+
CidLink,
59
+
Blob,
60
+
Array,
61
+
Object,
62
+
#[serde(untagged)]
63
+
String(LexiconStringType),
64
+
}
65
+
66
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
67
+
#[serde(rename_all = "kebab-case")]
68
+
pub enum LexiconType {
69
+
Params,
70
+
Token,
71
+
Ref,
72
+
Union,
73
+
Unknown,
74
+
Record,
75
+
Query,
76
+
Procedure,
77
+
Subscription,
78
+
#[serde(untagged)]
79
+
DataModel(DataModelType),
80
+
}
81
+
82
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
83
+
#[serde(rename_all = "kebab-case")]
84
+
pub enum LexiconStringType {
85
+
Datetime,
86
+
AtUri,
87
+
Did,
88
+
Handle,
89
+
AtIdentifier,
90
+
Nsid,
91
+
Cid,
92
+
Language,
93
+
Tid,
94
+
RecordKey,
95
+
Uri(UriType),
96
+
#[serde(untagged)]
97
+
String,
98
+
}
99
+
100
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
101
+
#[serde(tag = "type")]
102
+
pub enum UriType {
103
+
Did,
104
+
At,
105
+
Https,
106
+
Wss,
107
+
Cid,
108
+
Dns,
109
+
Any,
110
+
}
+84
-22
crates/jacquard-common/src/types/aturi.rs
+84
-22
crates/jacquard-common/src/types/aturi.rs
···
2
2
use crate::types::ident::AtIdentifier;
3
3
use crate::types::nsid::Nsid;
4
4
use crate::types::recordkey::{RecordKey, Rkey};
5
+
use crate::types::string::AtStrError;
5
6
use regex::Regex;
6
7
use serde::Serializer;
7
8
use serde::{Deserialize, Deserializer, Serialize, de::Error};
8
-
use smol_str::ToSmolStr;
9
+
use smol_str::{SmolStr, ToSmolStr};
9
10
use std::fmt;
10
11
use std::sync::LazyLock;
11
12
use std::{ops::Deref, str::FromStr};
···
41
42
42
43
impl<'u> AtUri<'u> {
43
44
/// Fallible constructor, validates, borrows from input
44
-
pub fn new(uri: &'u str) -> Result<Self, &'static str> {
45
+
pub fn new(uri: &'u str) -> Result<Self, AtStrError> {
45
46
if let Some(parts) = ATURI_REGEX.captures(uri) {
46
47
if let Some(authority) = parts.name("authority") {
47
-
let authority = AtIdentifier::new(authority.as_str())?;
48
+
let authority = AtIdentifier::new(authority.as_str())
49
+
.map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?;
48
50
let path = if let Some(collection) = parts.name("collection") {
49
-
let collection = Nsid::new(collection.as_str())?;
51
+
let collection = Nsid::new(collection.as_str())
52
+
.map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?;
50
53
let rkey = if let Some(rkey) = parts.name("rkey") {
51
-
let rkey = RecordKey::from(Rkey::new(rkey.as_str())?);
54
+
let rkey =
55
+
RecordKey::from(Rkey::new(rkey.as_str()).map_err(|e| {
56
+
AtStrError::wrap("at-uri-scheme", uri.to_string(), e)
57
+
})?);
52
58
Some(rkey)
53
59
} else {
54
60
None
···
68
74
fragment,
69
75
})
70
76
} else {
71
-
Err("at:// URI missing authority")
77
+
Err(AtStrError::missing("at-uri-scheme", uri, "authority"))
72
78
}
73
79
} else {
74
-
Err("Invalid at:// URI via regex")
80
+
Err(AtStrError::regex(
81
+
"at-uri-scheme",
82
+
uri,
83
+
SmolStr::new_static("doesn't match schema"),
84
+
))
75
85
}
76
86
}
77
87
78
-
pub fn new_owned(uri: impl AsRef<str>) -> Result<Self, &'static str> {
88
+
pub fn raw(uri: &'u str) -> Self {
89
+
if let Some(parts) = ATURI_REGEX.captures(uri) {
90
+
if let Some(authority) = parts.name("authority") {
91
+
let authority = AtIdentifier::raw(authority.as_str());
92
+
let path = if let Some(collection) = parts.name("collection") {
93
+
let collection = Nsid::raw(collection.as_str());
94
+
let rkey = if let Some(rkey) = parts.name("rkey") {
95
+
let rkey = RecordKey::from(Rkey::raw(rkey.as_str()));
96
+
Some(rkey)
97
+
} else {
98
+
None
99
+
};
100
+
Some(UriPath { collection, rkey })
101
+
} else {
102
+
None
103
+
};
104
+
let fragment = parts.name("fragment").map(|fragment| {
105
+
let fragment = CowStr::Borrowed(fragment.as_str());
106
+
fragment
107
+
});
108
+
AtUri {
109
+
uri: CowStr::Borrowed(uri),
110
+
authority,
111
+
path,
112
+
fragment,
113
+
}
114
+
} else {
115
+
panic!("at:// URI missing authority")
116
+
}
117
+
} else {
118
+
panic!("Invalid at:// URI via regex")
119
+
}
120
+
}
121
+
122
+
pub fn new_owned(uri: impl AsRef<str>) -> Result<Self, AtStrError> {
79
123
let uri = uri.as_ref();
80
124
if let Some(parts) = ATURI_REGEX.captures(uri) {
81
125
if let Some(authority) = parts.name("authority") {
82
-
let authority = AtIdentifier::new_owned(authority.as_str())?;
126
+
let authority = AtIdentifier::new_owned(authority.as_str())
127
+
.map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?;
83
128
let path = if let Some(collection) = parts.name("collection") {
84
-
let collection = Nsid::new_owned(collection.as_str())?;
129
+
let collection = Nsid::new_owned(collection.as_str())
130
+
.map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?;
85
131
let rkey = if let Some(rkey) = parts.name("rkey") {
86
-
let rkey = RecordKey::from(Rkey::new_owned(rkey.as_str())?);
132
+
let rkey =
133
+
RecordKey::from(Rkey::new_owned(rkey.as_str()).map_err(|e| {
134
+
AtStrError::wrap("at-uri-scheme", uri.to_string(), e)
135
+
})?);
87
136
Some(rkey)
88
137
} else {
89
138
None
···
103
152
fragment,
104
153
})
105
154
} else {
106
-
Err("at:// URI missing authority")
155
+
Err(AtStrError::missing("at-uri-scheme", uri, "authority"))
107
156
}
108
157
} else {
109
-
Err("Invalid at:// URI via regex")
158
+
Err(AtStrError::regex(
159
+
"at-uri-scheme",
160
+
uri,
161
+
SmolStr::new_static("doesn't match schema"),
162
+
))
110
163
}
111
164
}
112
165
113
-
pub fn new_static(uri: &'static str) -> Result<AtUri<'static>, &'static str> {
166
+
pub fn new_static(uri: &'static str) -> Result<AtUri<'static>, AtStrError> {
114
167
let uri = uri.as_ref();
115
168
if let Some(parts) = ATURI_REGEX.captures(uri) {
116
169
if let Some(authority) = parts.name("authority") {
117
-
let authority = AtIdentifier::new_static(authority.as_str())?;
170
+
let authority = AtIdentifier::new_static(authority.as_str())
171
+
.map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?;
118
172
let path = if let Some(collection) = parts.name("collection") {
119
-
let collection = Nsid::new_static(collection.as_str())?;
173
+
let collection = Nsid::new_static(collection.as_str())
174
+
.map_err(|e| AtStrError::wrap("at-uri-scheme", uri.to_string(), e))?;
120
175
let rkey = if let Some(rkey) = parts.name("rkey") {
121
-
let rkey = RecordKey::from(Rkey::new_static(rkey.as_str())?);
176
+
let rkey =
177
+
RecordKey::from(Rkey::new_static(rkey.as_str()).map_err(|e| {
178
+
AtStrError::wrap("at-uri-scheme", uri.to_string(), e)
179
+
})?);
122
180
Some(rkey)
123
181
} else {
124
182
None
···
138
196
fragment,
139
197
})
140
198
} else {
141
-
Err("at:// URI missing authority")
199
+
Err(AtStrError::missing("at-uri-scheme", uri, "authority"))
142
200
}
143
201
} else {
144
-
Err("Invalid at:// URI via regex")
202
+
Err(AtStrError::regex(
203
+
"at-uri-scheme",
204
+
uri,
205
+
SmolStr::new_static("doesn't match schema"),
206
+
))
145
207
}
146
208
}
147
209
···
198
260
}
199
261
200
262
impl FromStr for AtUri<'_> {
201
-
type Err = &'static str;
263
+
type Err = AtStrError;
202
264
203
265
/// Has to take ownership due to the lifetime constraints of the FromStr trait.
204
266
/// Prefer `AtUri::new()` or `AtUri::raw()` if you want to borrow.
···
245
307
}
246
308
247
309
impl TryFrom<String> for AtUri<'static> {
248
-
type Error = &'static str;
310
+
type Error = AtStrError;
249
311
250
312
fn try_from(value: String) -> Result<Self, Self::Error> {
251
313
Self::new_owned(&value)
···
253
315
}
254
316
255
317
impl<'d> TryFrom<CowStr<'d>> for AtUri<'d> {
256
-
type Error = &'static str;
318
+
type Error = AtStrError;
257
319
/// TODO: rewrite to avoid taking ownership/cloning
258
320
fn try_from(value: CowStr<'d>) -> Result<Self, Self::Error> {
259
321
Self::new_owned(value)
+1
-1
crates/jacquard-common/src/types/blob.rs
+1
-1
crates/jacquard-common/src/types/blob.rs
+12
-2
crates/jacquard-common/src/types/cid.rs
+12
-2
crates/jacquard-common/src/types/cid.rs
···
40
40
Ok(Self::Str(cid_str))
41
41
}
42
42
}
43
-
pub fn ipld(cid: IpldCid) -> Self {
43
+
44
+
pub fn new_owned(cid: &[u8]) -> Result<Cid<'static>, Error> {
45
+
if let Ok(cid) = IpldCid::try_from(cid.as_ref()) {
46
+
Ok(Self::ipld(cid))
47
+
} else {
48
+
let cid_str = CowStr::from_utf8(cid)?;
49
+
Ok(Cid::Str(cid_str.into_static()))
50
+
}
51
+
}
52
+
53
+
pub fn ipld(cid: IpldCid) -> Cid<'static> {
44
54
let s = CowStr::Owned(
45
55
cid.to_string_of_base(ATP_CID_BASE)
46
56
.unwrap_or_default()
47
57
.to_smolstr(),
48
58
);
49
-
Self::Ipld { cid, s }
59
+
Cid::Ipld { cid, s }
50
60
}
51
61
52
62
pub fn str(cid: &'c str) -> Self {
+2
-2
crates/jacquard-common/src/types/datetime.rs
+2
-2
crates/jacquard-common/src/types/datetime.rs
···
13
13
});
14
14
15
15
/// A Lexicon timestamp.
16
-
#[derive(Clone, Debug, Eq)]
16
+
#[derive(Clone, Debug, Eq, Hash)]
17
17
pub struct Datetime {
18
18
/// Serialized form. Preserved during parsing to ensure round-trip re-serialization.
19
19
serialized: CowStr<'static>,
···
98
98
if ISO8601_REGEX.is_match(s) {
99
99
let dt = chrono::DateTime::parse_from_rfc3339(s)?;
100
100
Ok(Self {
101
-
serialized: CowStr::Borrowed(s).into_static(),
101
+
serialized: CowStr::Owned(s.to_smolstr()),
102
102
dt,
103
103
})
104
104
} else {
+24
-11
crates/jacquard-common/src/types/did.rs
+24
-11
crates/jacquard-common/src/types/did.rs
···
1
+
use crate::types::string::AtStrError;
1
2
use crate::{CowStr, IntoStatic};
2
3
use regex::Regex;
3
4
use serde::{Deserialize, Deserializer, Serialize, de::Error};
4
-
use smol_str::ToSmolStr;
5
+
use smol_str::{SmolStr, ToSmolStr};
5
6
use std::fmt;
6
7
use std::sync::LazyLock;
7
8
use std::{ops::Deref, str::FromStr};
···
16
17
17
18
impl<'d> Did<'d> {
18
19
/// Fallible constructor, validates, borrows from input
19
-
pub fn new(did: &'d str) -> Result<Self, &'static str> {
20
+
pub fn new(did: &'d str) -> Result<Self, AtStrError> {
20
21
let did = did.strip_prefix("at://").unwrap_or(did);
21
22
if did.len() > 2048 {
22
-
Err("DID too long")
23
+
Err(AtStrError::too_long("did", did, 2048, did.len()))
23
24
} else if !DID_REGEX.is_match(did) {
24
-
Err("Invalid DID")
25
+
Err(AtStrError::regex(
26
+
"did",
27
+
did,
28
+
SmolStr::new_static("invalid"),
29
+
))
25
30
} else {
26
31
Ok(Self(CowStr::Borrowed(did)))
27
32
}
28
33
}
29
34
30
35
/// Fallible constructor, validates, takes ownership
31
-
pub fn new_owned(did: impl AsRef<str>) -> Result<Self, &'static str> {
36
+
pub fn new_owned(did: impl AsRef<str>) -> Result<Self, AtStrError> {
32
37
let did = did.as_ref();
33
38
let did = did.strip_prefix("at://").unwrap_or(did);
34
39
if did.len() > 2048 {
35
-
Err("DID too long")
40
+
Err(AtStrError::too_long("did", did, 2048, did.len()))
36
41
} else if !DID_REGEX.is_match(did) {
37
-
Err("Invalid DID")
42
+
Err(AtStrError::regex(
43
+
"did",
44
+
did,
45
+
SmolStr::new_static("invalid"),
46
+
))
38
47
} else {
39
48
Ok(Self(CowStr::Owned(did.to_smolstr())))
40
49
}
41
50
}
42
51
43
52
/// Fallible constructor, validates, doesn't allocate
44
-
pub fn new_static(did: &'static str) -> Result<Self, &'static str> {
53
+
pub fn new_static(did: &'static str) -> Result<Self, AtStrError> {
45
54
let did = did.strip_prefix("at://").unwrap_or(did);
46
55
if did.len() > 2048 {
47
-
Err("DID too long")
56
+
Err(AtStrError::too_long("did", did, 2048, did.len()))
48
57
} else if !DID_REGEX.is_match(did) {
49
-
Err("Invalid DID")
58
+
Err(AtStrError::regex(
59
+
"did",
60
+
did,
61
+
SmolStr::new_static("invalid"),
62
+
))
50
63
} else {
51
64
Ok(Self(CowStr::new_static(did)))
52
65
}
···
82
95
}
83
96
84
97
impl FromStr for Did<'_> {
85
-
type Err = &'static str;
98
+
type Err = AtStrError;
86
99
87
100
/// Has to take ownership due to the lifetime constraints of the FromStr trait.
88
101
/// Prefer `Did::new()` or `Did::raw` if you want to borrow.
+35
-43
crates/jacquard-common/src/types/handle.rs
+35
-43
crates/jacquard-common/src/types/handle.rs
···
1
+
use crate::types::string::AtStrError;
2
+
use crate::types::{DISALLOWED_TLDS, ends_with};
1
3
use crate::{CowStr, IntoStatic};
2
4
use regex::Regex;
3
5
use serde::{Deserialize, Deserializer, Serialize, de::Error};
4
-
use smol_str::ToSmolStr;
6
+
use smol_str::{SmolStr, ToSmolStr};
5
7
use std::fmt;
6
8
use std::sync::LazyLock;
7
9
use std::{ops::Deref, str::FromStr};
···
20
22
/// Fallible constructor, validates, borrows from input
21
23
///
22
24
/// Accepts (and strips) preceding '@' if present
23
-
pub fn new(handle: &'h str) -> Result<Self, &'static str> {
25
+
pub fn new(handle: &'h str) -> Result<Self, AtStrError> {
24
26
let handle = handle
25
27
.strip_prefix("at://")
26
28
.unwrap_or(handle)
27
29
.strip_prefix('@')
28
30
.unwrap_or(handle);
29
31
if handle.len() > 253 {
30
-
Err("handle too long")
32
+
Err(AtStrError::too_long("handle", handle, 253, handle.len()))
31
33
} else if !HANDLE_REGEX.is_match(handle) {
32
-
Err("Invalid handle")
34
+
Err(AtStrError::regex(
35
+
"handle",
36
+
handle,
37
+
SmolStr::new_static("invalid"),
38
+
))
39
+
} else if ends_with(handle, DISALLOWED_TLDS) {
40
+
Err(AtStrError::disallowed("handle", handle, DISALLOWED_TLDS))
33
41
} else {
34
42
Ok(Self(CowStr::Borrowed(handle)))
35
43
}
36
44
}
37
45
38
46
/// Fallible constructor, validates, takes ownership
39
-
pub fn new_owned(handle: impl AsRef<str>) -> Result<Self, &'static str> {
47
+
pub fn new_owned(handle: impl AsRef<str>) -> Result<Self, AtStrError> {
40
48
let handle = handle.as_ref();
41
49
let handle = handle
42
50
.strip_prefix("at://")
···
44
52
.strip_prefix('@')
45
53
.unwrap_or(handle);
46
54
if handle.len() > 253 {
47
-
Err("handle too long")
55
+
Err(AtStrError::too_long("handle", handle, 253, handle.len()))
48
56
} else if !HANDLE_REGEX.is_match(handle) {
49
-
Err("Invalid handle")
57
+
Err(AtStrError::regex(
58
+
"handle",
59
+
handle,
60
+
SmolStr::new_static("invalid"),
61
+
))
62
+
} else if ends_with(handle, DISALLOWED_TLDS) {
63
+
Err(AtStrError::disallowed("handle", handle, DISALLOWED_TLDS))
50
64
} else {
51
65
Ok(Self(CowStr::Owned(handle.to_smolstr())))
52
66
}
53
67
}
54
68
55
69
/// Fallible constructor, validates, doesn't allocate
56
-
pub fn new_static(handle: &'static str) -> Result<Self, &'static str> {
70
+
pub fn new_static(handle: &'static str) -> Result<Self, AtStrError> {
57
71
let handle = handle
58
72
.strip_prefix("at://")
59
73
.unwrap_or(handle)
60
74
.strip_prefix('@')
61
75
.unwrap_or(handle);
62
76
if handle.len() > 253 {
63
-
Err("handle too long")
77
+
Err(AtStrError::too_long("handle", handle, 253, handle.len()))
64
78
} else if !HANDLE_REGEX.is_match(handle) {
65
-
Err("Invalid handle")
79
+
Err(AtStrError::regex(
80
+
"handle",
81
+
handle,
82
+
SmolStr::new_static("invalid"),
83
+
))
84
+
} else if ends_with(handle, DISALLOWED_TLDS) {
85
+
Err(AtStrError::disallowed("handle", handle, DISALLOWED_TLDS))
66
86
} else {
67
87
Ok(Self(CowStr::new_static(handle)))
68
88
}
···
83
103
panic!("handle too long")
84
104
} else if !HANDLE_REGEX.is_match(handle) {
85
105
panic!("Invalid handle")
106
+
} else if ends_with(handle, DISALLOWED_TLDS) {
107
+
panic!("top-level domain not allowed in handles")
86
108
} else {
87
109
Self(CowStr::Borrowed(handle))
88
110
}
···
110
132
}
111
133
112
134
impl FromStr for Handle<'_> {
113
-
type Err = &'static str;
135
+
type Err = AtStrError;
114
136
115
137
/// Has to take ownership due to the lifetime constraints of the FromStr trait.
116
138
/// Prefer `Handle::new()` or `Handle::raw` if you want to borrow.
···
163
185
164
186
impl From<String> for Handle<'static> {
165
187
fn from(value: String) -> Self {
166
-
let value = if let Some(handle) = value
167
-
.strip_prefix("at://")
168
-
.unwrap_or(&value)
169
-
.strip_prefix('@')
170
-
{
171
-
CowStr::Borrowed(handle)
172
-
} else {
173
-
value.into()
174
-
};
175
-
if value.len() > 253 {
176
-
panic!("handle too long")
177
-
} else if !HANDLE_REGEX.is_match(&value) {
178
-
panic!("Invalid handle")
179
-
} else {
180
-
Self(value.into_static())
181
-
}
188
+
Self::new_owned(value).unwrap()
182
189
}
183
190
}
184
191
185
192
impl<'h> From<CowStr<'h>> for Handle<'h> {
186
193
fn from(value: CowStr<'h>) -> Self {
187
-
let value = if let Some(handle) = value
188
-
.strip_prefix("at://")
189
-
.unwrap_or(&value)
190
-
.strip_prefix('@')
191
-
{
192
-
CowStr::Borrowed(handle)
193
-
} else {
194
-
value
195
-
};
196
-
if value.len() > 253 {
197
-
panic!("handle too long")
198
-
} else if !HANDLE_REGEX.is_match(&value) {
199
-
panic!("Invalid handle")
200
-
} else {
201
-
Self(value.into_static())
202
-
}
194
+
Self::new_owned(value).unwrap()
203
195
}
204
196
}
205
197
+5
-4
crates/jacquard-common/src/types/ident.rs
+5
-4
crates/jacquard-common/src/types/ident.rs
···
1
1
use crate::types::handle::Handle;
2
+
use crate::types::string::AtStrError;
2
3
use crate::{IntoStatic, types::did::Did};
3
4
use std::fmt;
4
5
use std::str::FromStr;
···
18
19
19
20
impl<'i> AtIdentifier<'i> {
20
21
/// Fallible constructor, validates, borrows from input
21
-
pub fn new(ident: &'i str) -> Result<Self, &'static str> {
22
+
pub fn new(ident: &'i str) -> Result<Self, AtStrError> {
22
23
if let Ok(did) = ident.parse() {
23
24
Ok(AtIdentifier::Did(did))
24
25
} else {
···
27
28
}
28
29
29
30
/// Fallible constructor, validates, takes ownership
30
-
pub fn new_owned(ident: impl AsRef<str>) -> Result<Self, &'static str> {
31
+
pub fn new_owned(ident: impl AsRef<str>) -> Result<Self, AtStrError> {
31
32
let ident = ident.as_ref();
32
33
if let Ok(did) = Did::new_owned(ident) {
33
34
Ok(AtIdentifier::Did(did))
···
37
38
}
38
39
39
40
/// Fallible constructor, validates, doesn't allocate
40
-
pub fn new_static(ident: &'static str) -> Result<AtIdentifier<'static>, &'static str> {
41
+
pub fn new_static(ident: &'static str) -> Result<AtIdentifier<'static>, AtStrError> {
41
42
if let Ok(did) = Did::new_static(ident) {
42
43
Ok(AtIdentifier::Did(did))
43
44
} else {
···
93
94
}
94
95
95
96
impl FromStr for AtIdentifier<'_> {
96
-
type Err = &'static str;
97
+
type Err = AtStrError;
97
98
98
99
fn from_str(s: &str) -> Result<Self, Self::Err> {
99
100
if let Ok(did) = s.parse() {
+19
-17
crates/jacquard-common/src/types/language.rs
+19
-17
crates/jacquard-common/src/types/language.rs
···
5
5
6
6
use crate::CowStr;
7
7
8
-
/// A [Timestamp Identifier].
8
+
/// An IETF language tag.
9
+
///
10
+
/// Uses langtag crate for validation, but is stored as a SmolStr for size/avoiding allocations
9
11
///
10
-
/// [Timestamp Identifier]: https://atproto.com/specs/lang
12
+
/// TODO: Implement langtag-style semantic matching for this type, delegating to langtag
11
13
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)]
12
14
#[serde(transparent)]
13
15
#[repr(transparent)]
14
-
pub struct Lang(SmolStr);
16
+
pub struct Language(SmolStr);
15
17
16
-
impl Lang {
18
+
impl Language {
17
19
/// Parses an IETF language tag from the given string.
18
20
pub fn new<T>(lang: &T) -> Result<Self, langtag::InvalidLangTag<&T>>
19
21
where
20
22
T: AsRef<str> + ?Sized,
21
23
{
22
24
let tag = langtag::LangTag::new(lang)?;
23
-
Ok(Lang(SmolStr::new_inline(tag.as_str())))
25
+
Ok(Language(SmolStr::new_inline(tag.as_str())))
24
26
}
25
27
26
28
/// Infallible constructor for when you *know* the string is a valid IETF language tag.
···
30
32
pub fn raw(lang: impl AsRef<str>) -> Self {
31
33
let lang = lang.as_ref();
32
34
let tag = langtag::LangTag::new(lang).expect("valid IETF language tag");
33
-
Lang(SmolStr::new_inline(tag.as_str()))
35
+
Language(SmolStr::new_inline(tag.as_str()))
34
36
}
35
37
36
38
/// Infallible constructor for when you *know* the string is a valid IETF language tag.
···
49
51
}
50
52
}
51
53
52
-
impl FromStr for Lang {
54
+
impl FromStr for Language {
53
55
type Err = SmolStr;
54
56
55
57
fn from_str(s: &str) -> Result<Self, Self::Err> {
···
57
59
}
58
60
}
59
61
60
-
impl<'de> Deserialize<'de> for Lang {
62
+
impl<'de> Deserialize<'de> for Language {
61
63
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62
64
where
63
65
D: Deserializer<'de>,
···
67
69
}
68
70
}
69
71
70
-
impl fmt::Display for Lang {
72
+
impl fmt::Display for Language {
71
73
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72
74
f.write_str(&self.0)
73
75
}
74
76
}
75
77
76
-
impl From<Lang> for String {
77
-
fn from(value: Lang) -> Self {
78
+
impl From<Language> for String {
79
+
fn from(value: Language) -> Self {
78
80
value.0.to_string()
79
81
}
80
82
}
81
83
82
-
impl From<Lang> for SmolStr {
83
-
fn from(value: Lang) -> Self {
84
+
impl From<Language> for SmolStr {
85
+
fn from(value: Language) -> Self {
84
86
value.0
85
87
}
86
88
}
87
89
88
-
impl From<String> for Lang {
90
+
impl From<String> for Language {
89
91
fn from(value: String) -> Self {
90
92
Self::raw(&value)
91
93
}
92
94
}
93
95
94
-
impl<'t> From<CowStr<'t>> for Lang {
96
+
impl<'t> From<CowStr<'t>> for Language {
95
97
fn from(value: CowStr<'t>) -> Self {
96
98
Self::raw(&value)
97
99
}
98
100
}
99
101
100
-
impl AsRef<str> for Lang {
102
+
impl AsRef<str> for Language {
101
103
fn as_ref(&self) -> &str {
102
104
self.as_str()
103
105
}
104
106
}
105
107
106
-
impl Deref for Lang {
108
+
impl Deref for Language {
107
109
type Target = str;
108
110
109
111
fn deref(&self) -> &Self::Target {
+23
-10
crates/jacquard-common/src/types/nsid.rs
+23
-10
crates/jacquard-common/src/types/nsid.rs
···
1
1
use crate::types::recordkey::RecordKeyType;
2
+
use crate::types::string::AtStrError;
2
3
use crate::{CowStr, IntoStatic};
3
4
use regex::Regex;
4
5
use serde::{Deserialize, Deserializer, Serialize, de::Error};
···
22
23
23
24
impl<'n> Nsid<'n> {
24
25
/// Fallible constructor, validates, borrows from input
25
-
pub fn new(nsid: &'n str) -> Result<Self, &'static str> {
26
+
pub fn new(nsid: &'n str) -> Result<Self, AtStrError> {
26
27
if nsid.len() > 317 {
27
-
Err("NSID too long")
28
+
Err(AtStrError::too_long("nsid", nsid, 317, nsid.len()))
28
29
} else if !NSID_REGEX.is_match(nsid) {
29
-
Err("Invalid NSID")
30
+
Err(AtStrError::regex(
31
+
"nsid",
32
+
nsid,
33
+
SmolStr::new_static("invalid"),
34
+
))
30
35
} else {
31
36
Ok(Self(CowStr::Borrowed(nsid)))
32
37
}
33
38
}
34
39
35
40
/// Fallible constructor, validates, borrows from input
36
-
pub fn new_owned(nsid: impl AsRef<str>) -> Result<Self, &'static str> {
41
+
pub fn new_owned(nsid: impl AsRef<str>) -> Result<Self, AtStrError> {
37
42
let nsid = nsid.as_ref();
38
43
if nsid.len() > 317 {
39
-
Err("NSID too long")
44
+
Err(AtStrError::too_long("nsid", nsid, 317, nsid.len()))
40
45
} else if !NSID_REGEX.is_match(nsid) {
41
-
Err("Invalid NSID")
46
+
Err(AtStrError::regex(
47
+
"nsid",
48
+
nsid,
49
+
SmolStr::new_static("invalid"),
50
+
))
42
51
} else {
43
52
Ok(Self(CowStr::Owned(nsid.to_smolstr())))
44
53
}
45
54
}
46
55
47
56
/// Fallible constructor, validates, doesn't allocate
48
-
pub fn new_static(nsid: &'static str) -> Result<Self, &'static str> {
57
+
pub fn new_static(nsid: &'static str) -> Result<Self, AtStrError> {
49
58
if nsid.len() > 317 {
50
-
Err("NSID too long")
59
+
Err(AtStrError::too_long("nsid", nsid, 317, nsid.len()))
51
60
} else if !NSID_REGEX.is_match(nsid) {
52
-
Err("Invalid NSID")
61
+
Err(AtStrError::regex(
62
+
"nsid",
63
+
nsid,
64
+
SmolStr::new_static("invalid"),
65
+
))
53
66
} else {
54
67
Ok(Self(CowStr::new_static(nsid)))
55
68
}
···
96
109
}
97
110
98
111
impl<'n> FromStr for Nsid<'n> {
99
-
type Err = &'static str;
112
+
type Err = AtStrError;
100
113
101
114
/// Has to take ownership due to the lifetime constraints of the FromStr trait.
102
115
/// Prefer `Nsid::new()` or `Nsid::raw` if you want to borrow.
+29
-12
crates/jacquard-common/src/types/recordkey.rs
+29
-12
crates/jacquard-common/src/types/recordkey.rs
···
1
1
use crate::types::Literal;
2
+
use crate::types::string::AtStrError;
2
3
use crate::{CowStr, IntoStatic};
3
4
use regex::Regex;
4
5
use serde::{Deserialize, Deserializer, Serialize, de::Error};
···
74
75
/// AT Protocol rkey
75
76
impl<'r> Rkey<'r> {
76
77
/// Fallible constructor, validates, borrows from input
77
-
pub fn new(rkey: &'r str) -> Result<Self, &'static str> {
78
+
pub fn new(rkey: &'r str) -> Result<Self, AtStrError> {
78
79
if [".", ".."].contains(&rkey) {
79
-
Err("Disallowed rkey")
80
+
Err(AtStrError::disallowed("record-key", rkey, &[".", ".."]))
80
81
} else if !RKEY_REGEX.is_match(rkey) {
81
-
Err("Invalid rkey")
82
+
Err(AtStrError::regex(
83
+
"record-key",
84
+
rkey,
85
+
SmolStr::new_static("doesn't match 'any' schema"),
86
+
))
82
87
} else {
83
88
Ok(Self(CowStr::Borrowed(rkey)))
84
89
}
85
90
}
86
91
87
92
/// Fallible constructor, validates, borrows from input
88
-
pub fn new_owned(rkey: impl AsRef<str>) -> Result<Self, &'static str> {
93
+
pub fn new_owned(rkey: impl AsRef<str>) -> Result<Self, AtStrError> {
89
94
let rkey = rkey.as_ref();
90
95
if [".", ".."].contains(&rkey) {
91
-
Err("Disallowed rkey")
96
+
Err(AtStrError::disallowed("record-key", rkey, &[".", ".."]))
92
97
} else if !RKEY_REGEX.is_match(rkey) {
93
-
Err("Invalid rkey")
98
+
Err(AtStrError::regex(
99
+
"record-key",
100
+
rkey,
101
+
SmolStr::new_static("doesn't match 'any' schema"),
102
+
))
94
103
} else {
95
104
Ok(Self(CowStr::Owned(rkey.to_smolstr())))
96
105
}
97
106
}
98
107
99
108
/// Fallible constructor, validates, doesn't allocate
100
-
pub fn new_static(rkey: &'static str) -> Result<Self, &'static str> {
109
+
pub fn new_static(rkey: &'static str) -> Result<Self, AtStrError> {
101
110
if [".", ".."].contains(&rkey) {
102
-
Err("Disallowed rkey")
111
+
Err(AtStrError::disallowed("record-key", rkey, &[".", ".."]))
103
112
} else if !RKEY_REGEX.is_match(rkey) {
104
-
Err("Invalid rkey")
113
+
Err(AtStrError::regex(
114
+
"record-key",
115
+
rkey,
116
+
SmolStr::new_static("doesn't match 'any' schema"),
117
+
))
105
118
} else {
106
119
Ok(Self(CowStr::new_static(rkey)))
107
120
}
···
136
149
}
137
150
138
151
impl<'r> FromStr for Rkey<'r> {
139
-
type Err = &'static str;
152
+
type Err = AtStrError;
140
153
141
154
fn from_str(s: &str) -> Result<Self, Self::Err> {
142
155
if [".", ".."].contains(&s) {
143
-
Err("Disallowed rkey")
156
+
Err(AtStrError::disallowed("record-key", s, &[".", ".."]))
144
157
} else if !RKEY_REGEX.is_match(s) {
145
-
Err("Invalid rkey")
158
+
Err(AtStrError::regex(
159
+
"record-key",
160
+
s,
161
+
SmolStr::new_static("doesn't match 'any' schema"),
162
+
))
146
163
} else {
147
164
Ok(Self(CowStr::Owned(s.to_smolstr())))
148
165
}
+394
crates/jacquard-common/src/types/string.rs
+394
crates/jacquard-common/src/types/string.rs
···
1
+
use bytes::Bytes;
2
+
use miette::SourceSpan;
3
+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
+
use smol_str::{SmolStr, ToSmolStr};
5
+
use std::{collections::BTreeMap, str::FromStr, sync::Arc};
6
+
7
+
use crate::IntoStatic;
8
+
pub use crate::{
9
+
CowStr,
10
+
types::{
11
+
aturi::AtUri,
12
+
cid::Cid,
13
+
datetime::Datetime,
14
+
did::Did,
15
+
handle::Handle,
16
+
ident::AtIdentifier,
17
+
language::Language,
18
+
nsid::Nsid,
19
+
recordkey::{RecordKey, Rkey},
20
+
tid::Tid,
21
+
uri::Uri,
22
+
},
23
+
};
24
+
25
+
/// ATProto string value
26
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27
+
pub enum AtprotoStr<'s> {
28
+
Datetime(Datetime),
29
+
Language(Language),
30
+
Tid(Tid),
31
+
Nsid(Nsid<'s>),
32
+
Did(Did<'s>),
33
+
Handle(Handle<'s>),
34
+
AtIdentifier(AtIdentifier<'s>),
35
+
AtUri(AtUri<'s>),
36
+
Uri(Uri<'s>),
37
+
Cid(Cid<'s>),
38
+
RecordKey(RecordKey<Rkey<'s>>),
39
+
String(CowStr<'s>),
40
+
}
41
+
42
+
impl<'s> AtprotoStr<'s> {
43
+
/// Borrowing constructor for bare atproto string values
44
+
/// This is fairly exhaustive and potentially **slow**, prefer using anything
45
+
/// that narrows down the search field quicker.
46
+
///
47
+
/// Note: We don't construct record keys from bare strings in this because
48
+
/// the type is too permissive and too many things would be classified as rkeys.
49
+
///
50
+
/// Value object deserialization checks against the field names for common
51
+
/// names (uri, cid, did, handle, createdAt, indexedAt, etc.) to improve
52
+
/// performance of the happy path.
53
+
pub fn new(string: &'s str) -> Self {
54
+
// TODO: do some quick prefix checks like in Uri to drop through faster
55
+
if let Ok(datetime) = Datetime::from_str(string) {
56
+
Self::Datetime(datetime)
57
+
} else if let Ok(lang) = Language::new(string) {
58
+
Self::Language(lang)
59
+
} else if let Ok(tid) = Tid::from_str(string) {
60
+
Self::Tid(tid)
61
+
} else if let Ok(did) = Did::new(string) {
62
+
Self::Did(did)
63
+
} else if let Ok(handle) = Handle::new(string) {
64
+
Self::Handle(handle)
65
+
} else if let Ok(atid) = AtIdentifier::new(string) {
66
+
Self::AtIdentifier(atid)
67
+
} else if let Ok(nsid) = Nsid::new(string) {
68
+
Self::Nsid(nsid)
69
+
} else if let Ok(aturi) = AtUri::new(string) {
70
+
Self::AtUri(aturi)
71
+
} else if let Ok(uri) = Uri::new(string) {
72
+
Self::Uri(uri)
73
+
} else if let Ok(cid) = Cid::new(string.as_bytes()) {
74
+
Self::Cid(cid)
75
+
} else {
76
+
// We don't construct record keys from bare strings because the type is too permissive
77
+
Self::String(CowStr::Borrowed(string))
78
+
}
79
+
}
80
+
81
+
pub fn as_str(&self) -> &str {
82
+
match self {
83
+
Self::Datetime(datetime) => datetime.as_str(),
84
+
Self::Language(lang) => lang.as_str(),
85
+
Self::Handle(handle) => handle.as_str(),
86
+
Self::AtIdentifier(atid) => atid.as_str(),
87
+
Self::Nsid(nsid) => nsid.as_str(),
88
+
Self::AtUri(aturi) => aturi.as_str(),
89
+
Self::Uri(uri) => uri.as_str(),
90
+
Self::Cid(cid) => cid.as_str(),
91
+
Self::Tid(tid) => tid.as_str(),
92
+
Self::Did(did) => did.as_str(),
93
+
Self::RecordKey(rkey) => rkey.as_ref(),
94
+
Self::String(string) => string.as_ref(),
95
+
}
96
+
}
97
+
}
98
+
99
+
impl AtprotoStr<'static> {
100
+
/// Owned constructor for bare atproto string values
101
+
/// This is fairly exhaustive and potentially **slow**, prefer using anything
102
+
/// that narrows down the search field quicker.
103
+
///
104
+
/// Note: We don't construct record keys from bare strings in this because
105
+
/// the type is too permissive and too many things would be classified as rkeys.
106
+
///
107
+
/// Value object deserialization checks against the field names for common
108
+
/// names (uri, cid, did, handle, createdAt, indexedAt, etc.) to improve
109
+
/// performance of the happy path.
110
+
pub fn new_owned(string: impl AsRef<str>) -> AtprotoStr<'static> {
111
+
let string = string.as_ref();
112
+
// TODO: do some quick prefix checks like in Uri to drop through faster
113
+
if let Ok(datetime) = Datetime::from_str(string) {
114
+
Self::Datetime(datetime)
115
+
} else if let Ok(lang) = Language::new(string) {
116
+
Self::Language(lang)
117
+
} else if let Ok(tid) = Tid::from_str(string) {
118
+
Self::Tid(tid)
119
+
} else if let Ok(did) = Did::new_owned(string) {
120
+
Self::Did(did)
121
+
} else if let Ok(handle) = Handle::new_owned(string) {
122
+
Self::Handle(handle)
123
+
} else if let Ok(atid) = AtIdentifier::new_owned(string) {
124
+
Self::AtIdentifier(atid)
125
+
} else if let Ok(nsid) = Nsid::new_owned(string) {
126
+
Self::Nsid(nsid)
127
+
} else if let Ok(aturi) = AtUri::new_owned(string) {
128
+
Self::AtUri(aturi)
129
+
} else if let Ok(uri) = Uri::new_owned(string) {
130
+
Self::Uri(uri)
131
+
} else if let Ok(cid) = Cid::new_owned(string.as_bytes()) {
132
+
Self::Cid(cid)
133
+
} else {
134
+
// We don't construct record keys from bare strings because the type is too permissive
135
+
Self::String(CowStr::Owned(string.to_smolstr()))
136
+
}
137
+
}
138
+
}
139
+
140
+
impl<'s> AsRef<str> for AtprotoStr<'s> {
141
+
fn as_ref(&self) -> &str {
142
+
match self {
143
+
Self::Datetime(datetime) => datetime.as_str(),
144
+
Self::Language(lang) => lang.as_ref(),
145
+
Self::Tid(tid) => tid.as_ref(),
146
+
Self::Did(did) => did.as_ref(),
147
+
Self::Handle(handle) => handle.as_ref(),
148
+
Self::AtIdentifier(atid) => atid.as_ref(),
149
+
Self::Nsid(nsid) => nsid.as_ref(),
150
+
Self::AtUri(aturi) => aturi.as_ref(),
151
+
Self::Uri(uri) => uri.as_str(),
152
+
Self::Cid(cid) => cid.as_ref(),
153
+
Self::RecordKey(rkey) => rkey.as_ref(),
154
+
Self::String(string) => string.as_ref(),
155
+
}
156
+
}
157
+
}
158
+
159
+
impl Serialize for AtprotoStr<'_> {
160
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161
+
where
162
+
S: Serializer,
163
+
{
164
+
serializer.serialize_str(self.as_ref())
165
+
}
166
+
}
167
+
168
+
impl<'de> Deserialize<'de> for AtprotoStr<'de> {
169
+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170
+
where
171
+
D: Deserializer<'de>,
172
+
{
173
+
let value = Deserialize::deserialize(deserializer)?;
174
+
Ok(Self::new(value))
175
+
}
176
+
}
177
+
178
+
impl IntoStatic for AtprotoStr<'_> {
179
+
type Output = AtprotoStr<'static>;
180
+
181
+
fn into_static(self) -> Self::Output {
182
+
match self {
183
+
AtprotoStr::Datetime(datetime) => AtprotoStr::Datetime(datetime),
184
+
AtprotoStr::Language(language) => AtprotoStr::Language(language),
185
+
AtprotoStr::Tid(tid) => AtprotoStr::Tid(tid),
186
+
AtprotoStr::Nsid(nsid) => AtprotoStr::Nsid(nsid.into_static()),
187
+
AtprotoStr::Did(did) => AtprotoStr::Did(did.into_static()),
188
+
AtprotoStr::Handle(handle) => AtprotoStr::Handle(handle.into_static()),
189
+
AtprotoStr::AtIdentifier(ident) => AtprotoStr::AtIdentifier(ident.into_static()),
190
+
AtprotoStr::AtUri(at_uri) => {
191
+
AtprotoStr::AtUri(AtUri::new_owned(at_uri.as_str()).unwrap())
192
+
}
193
+
AtprotoStr::Uri(uri) => AtprotoStr::Uri(uri.into_static()),
194
+
AtprotoStr::Cid(cid) => AtprotoStr::Cid(cid.into_static()),
195
+
AtprotoStr::RecordKey(record_key) => AtprotoStr::RecordKey(record_key.into_static()),
196
+
AtprotoStr::String(cow_str) => AtprotoStr::String(cow_str.into_static()),
197
+
}
198
+
}
199
+
}
200
+
201
+
/// Parsing Error for atproto string types which don't have third-party specs
202
+
/// (e.g. datetime, CIDs, language tags).
203
+
///
204
+
/// `spec` refers to the final url path segment on atproto.com/specs,
205
+
/// detailing the specification for the type
206
+
/// `source` is the source string, or part of it
207
+
/// `kind` is the type of parsing error: `[StrParseKind]`
208
+
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
209
+
#[error("error in `{source}`: {kind}")]
210
+
#[diagnostic(
211
+
url("https://atproto.com/specs/{spec}"),
212
+
help("if something doesn't match the spec, contact the crate author")
213
+
)]
214
+
pub struct AtStrError {
215
+
pub spec: SmolStr,
216
+
#[source_code]
217
+
source: String,
218
+
#[source]
219
+
#[diagnostic_source]
220
+
pub kind: StrParseKind,
221
+
}
222
+
223
+
impl AtStrError {
224
+
pub fn new(spec: &'static str, source: String, kind: StrParseKind) -> Self {
225
+
Self {
226
+
spec: SmolStr::new_static(spec),
227
+
source,
228
+
kind,
229
+
}
230
+
}
231
+
232
+
pub fn wrap(spec: &'static str, source: String, error: AtStrError) -> Self {
233
+
if let Some(span) = match &error.kind {
234
+
StrParseKind::Disallowed { problem, .. } => problem,
235
+
StrParseKind::MissingComponent { span, .. } => span,
236
+
_ => &None,
237
+
} {
238
+
Self {
239
+
spec: SmolStr::new_static(spec),
240
+
source,
241
+
kind: StrParseKind::Wrap {
242
+
span: Some(*span),
243
+
err: Arc::new(error),
244
+
},
245
+
}
246
+
} else {
247
+
let span = source
248
+
.find(&error.source)
249
+
.map(|start| (start, error.source.len()).into());
250
+
Self {
251
+
spec: SmolStr::new_static(spec),
252
+
source,
253
+
kind: StrParseKind::Wrap {
254
+
span,
255
+
err: Arc::new(error),
256
+
},
257
+
}
258
+
}
259
+
}
260
+
261
+
/// something on the provided disallowed list was found in the source string
262
+
/// does a substring search for any of the offending strings to get the span
263
+
pub fn disallowed(spec: &'static str, source: &str, disallowed: &[&str]) -> Self {
264
+
for item in disallowed {
265
+
if let Some(loc) = source.find(item) {
266
+
return Self {
267
+
spec: SmolStr::new_static(spec),
268
+
source: source.to_string(),
269
+
kind: StrParseKind::Disallowed {
270
+
problem: Some(SourceSpan::new(loc.into(), item.len())),
271
+
message: smol_str::format_smolstr!("`{item}`"),
272
+
},
273
+
};
274
+
}
275
+
}
276
+
Self {
277
+
spec: SmolStr::new_static(spec),
278
+
source: source.to_string(),
279
+
kind: StrParseKind::Disallowed {
280
+
problem: None,
281
+
message: SmolStr::new_static(""),
282
+
},
283
+
}
284
+
}
285
+
286
+
pub fn too_long(spec: &'static str, source: &str, max: usize, actual: usize) -> Self {
287
+
Self {
288
+
spec: SmolStr::new_static(spec),
289
+
source: source.to_string(),
290
+
kind: StrParseKind::TooLong { max, actual },
291
+
}
292
+
}
293
+
294
+
pub fn too_short(spec: &'static str, source: &str, min: usize, actual: usize) -> Self {
295
+
Self {
296
+
spec: SmolStr::new_static(spec),
297
+
source: source.to_string(),
298
+
kind: StrParseKind::TooShort { min, actual },
299
+
}
300
+
}
301
+
302
+
/// missing component, with what was expected to be found
303
+
pub fn missing(spec: &'static str, source: &str, expected: &str) -> Self {
304
+
if let Some(loc) = source.find(expected) {
305
+
return Self {
306
+
spec: SmolStr::new_static(spec),
307
+
source: source.to_string(),
308
+
kind: StrParseKind::MissingComponent {
309
+
span: Some(SourceSpan::new(loc.into(), expected.len())),
310
+
message: smol_str::format_smolstr!("`{expected}` found incorrectly here"),
311
+
},
312
+
};
313
+
}
314
+
Self {
315
+
spec: SmolStr::new_static(spec),
316
+
source: source.to_string(),
317
+
kind: StrParseKind::MissingComponent {
318
+
span: None,
319
+
message: SmolStr::new(expected),
320
+
},
321
+
}
322
+
}
323
+
324
+
/// missing component, with the span where it was expected to be founf
325
+
pub fn missing_from(
326
+
spec: &'static str,
327
+
source: &str,
328
+
expected: &str,
329
+
span: (usize, usize),
330
+
) -> Self {
331
+
Self {
332
+
spec: SmolStr::new_static(spec),
333
+
source: source.to_string(),
334
+
kind: StrParseKind::MissingComponent {
335
+
span: Some(span.into()),
336
+
message: SmolStr::new(expected),
337
+
},
338
+
}
339
+
}
340
+
341
+
pub fn regex(spec: &'static str, source: &str, message: SmolStr) -> Self {
342
+
Self {
343
+
spec: SmolStr::new_static(spec),
344
+
source: source.to_string(),
345
+
kind: StrParseKind::RegexFail {
346
+
span: None,
347
+
message,
348
+
},
349
+
}
350
+
}
351
+
}
352
+
353
+
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
354
+
pub enum StrParseKind {
355
+
#[error("regex failure - {message}")]
356
+
#[diagnostic(code(jacquard::types::string::regex_fail))]
357
+
RegexFail {
358
+
#[label]
359
+
span: Option<SourceSpan>,
360
+
#[help]
361
+
message: SmolStr,
362
+
},
363
+
#[error("string too long (allowed: {max}, actual: {actual})")]
364
+
#[diagnostic(code(jacquard::types::string::wrong_length))]
365
+
TooLong { max: usize, actual: usize },
366
+
367
+
#[error("string too short (allowed: {min}, actual: {actual})")]
368
+
#[diagnostic(code(jacquard::types::string::wrong_length))]
369
+
TooShort { min: usize, actual: usize },
370
+
#[error("disallowed - {message}")]
371
+
#[diagnostic(code(jacquard::types::string::disallowed))]
372
+
Disallowed {
373
+
#[label]
374
+
problem: Option<SourceSpan>,
375
+
#[help]
376
+
message: SmolStr,
377
+
},
378
+
#[error("missing - {message}")]
379
+
#[diagnostic(code(jacquard::atstr::missing_component))]
380
+
MissingComponent {
381
+
#[label]
382
+
span: Option<SourceSpan>,
383
+
#[help]
384
+
message: SmolStr,
385
+
},
386
+
#[error("{err:?}")]
387
+
#[diagnostic(code(jacquard::atstr::inner))]
388
+
Wrap {
389
+
#[label]
390
+
span: Option<SourceSpan>,
391
+
#[source]
392
+
err: Arc<AtStrError>,
393
+
},
394
+
}
+20
-4
crates/jacquard-common/src/types/tid.rs
+20
-4
crates/jacquard-common/src/types/tid.rs
···
6
6
7
7
use crate::CowStr;
8
8
use crate::types::integer::LimitedU32;
9
+
use crate::types::string::{AtStrError, StrParseKind};
9
10
use regex::Regex;
10
11
11
12
fn s32_encode(mut i: u64) -> SmolStr {
···
40
41
41
42
impl Tid {
42
43
/// Parses a `TID` from the given string.
43
-
pub fn new(tid: impl AsRef<str>) -> Result<Self, &'static str> {
44
+
pub fn new(tid: impl AsRef<str>) -> Result<Self, AtStrError> {
44
45
let tid = tid.as_ref();
45
46
if tid.len() != 13 {
46
-
Err("TID must be 13 characters")
47
+
let kind = if tid.len() > 13 {
48
+
StrParseKind::TooLong {
49
+
max: 13,
50
+
actual: tid.len(),
51
+
}
52
+
} else {
53
+
StrParseKind::TooShort {
54
+
min: 13,
55
+
actual: tid.len(),
56
+
}
57
+
};
58
+
Err(AtStrError::new("tid", tid.to_string(), kind))
47
59
} else if !TID_REGEX.is_match(&tid.as_ref()) {
48
-
Err("Invalid TID")
60
+
let kind = StrParseKind::RegexFail {
61
+
span: None,
62
+
message: SmolStr::new_static("didn't match schema"),
63
+
};
64
+
Err(AtStrError::new("tid", tid.to_string(), kind))
49
65
} else {
50
66
Ok(Self(SmolStr::new_inline(&tid)))
51
67
}
···
117
133
}
118
134
119
135
impl FromStr for Tid {
120
-
type Err = &'static str;
136
+
type Err = AtStrError;
121
137
122
138
/// Has to take ownership due to the lifetime constraints of the FromStr trait.
123
139
/// Prefer `Did::new()` or `Did::raw` if you want to borrow.
+124
crates/jacquard-common/src/types/uri.rs
+124
crates/jacquard-common/src/types/uri.rs
···
1
+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
+
use smol_str::ToSmolStr;
3
+
use url::Url;
4
+
5
+
use crate::{
6
+
CowStr, IntoStatic,
7
+
types::{aturi::AtUri, cid::Cid, did::Did, string::AtStrError},
8
+
};
9
+
10
+
/// URI with best-available contextual type
11
+
/// TODO: figure out wtf a DNS uri should look like
12
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13
+
pub enum Uri<'u> {
14
+
Did(Did<'u>),
15
+
At(AtUri<'u>),
16
+
Https(Url),
17
+
Wss(Url),
18
+
Cid(Cid<'u>),
19
+
Any(CowStr<'u>),
20
+
}
21
+
22
+
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
23
+
pub enum UriParseError {
24
+
#[error("Invalid atproto string: {0}")]
25
+
At(#[from] AtStrError),
26
+
#[error(transparent)]
27
+
Url(#[from] url::ParseError),
28
+
#[error(transparent)]
29
+
Cid(#[from] crate::types::cid::Error),
30
+
}
31
+
32
+
impl<'u> Uri<'u> {
33
+
pub fn new(uri: &'u str) -> Result<Self, UriParseError> {
34
+
if uri.starts_with("did:") {
35
+
Ok(Uri::Did(Did::new(uri)?))
36
+
} else if uri.starts_with("at://") {
37
+
Ok(Uri::At(AtUri::new(uri)?))
38
+
} else if uri.starts_with("https://") {
39
+
Ok(Uri::Https(Url::parse(uri)?))
40
+
} else if uri.starts_with("wss://") {
41
+
Ok(Uri::Https(Url::parse(uri)?))
42
+
} else if uri.starts_with("ipld://") {
43
+
Ok(Uri::Cid(Cid::new(uri.as_bytes())?))
44
+
} else {
45
+
Ok(Uri::Any(CowStr::Borrowed(uri)))
46
+
}
47
+
}
48
+
49
+
pub fn new_owned(uri: impl AsRef<str>) -> Result<Uri<'static>, UriParseError> {
50
+
let uri = uri.as_ref();
51
+
if uri.starts_with("did:") {
52
+
Ok(Uri::Did(Did::new_owned(uri)?))
53
+
} else if uri.starts_with("at://") {
54
+
Ok(Uri::At(AtUri::new_owned(uri)?))
55
+
} else if uri.starts_with("https://") {
56
+
Ok(Uri::Https(Url::parse(uri)?))
57
+
} else if uri.starts_with("wss://") {
58
+
Ok(Uri::Https(Url::parse(uri)?))
59
+
} else if uri.starts_with("ipld://") {
60
+
Ok(Uri::Cid(Cid::new_owned(uri.as_bytes())?))
61
+
} else {
62
+
Ok(Uri::Any(CowStr::Owned(uri.to_smolstr())))
63
+
}
64
+
}
65
+
66
+
pub fn as_str(&self) -> &str {
67
+
match self {
68
+
Uri::Did(did) => did.as_str(),
69
+
Uri::At(at_uri) => at_uri.as_str(),
70
+
Uri::Https(url) => url.as_str(),
71
+
Uri::Wss(url) => url.as_str(),
72
+
Uri::Cid(cid) => cid.as_str(),
73
+
Uri::Any(s) => s.as_ref(),
74
+
}
75
+
}
76
+
}
77
+
78
+
impl Serialize for Uri<'_> {
79
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
80
+
where
81
+
S: Serializer,
82
+
{
83
+
serializer.serialize_str(self.as_str())
84
+
}
85
+
}
86
+
87
+
impl<'de> Deserialize<'de> for Uri<'de> {
88
+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
89
+
where
90
+
D: Deserializer<'de>,
91
+
{
92
+
use serde::de::Error;
93
+
let value = Deserialize::deserialize(deserializer)?;
94
+
Self::new(value).map_err(D::Error::custom)
95
+
}
96
+
}
97
+
98
+
impl<'s> AsRef<str> for Uri<'s> {
99
+
fn as_ref(&self) -> &str {
100
+
match self {
101
+
Uri::Did(did) => did.as_str(),
102
+
Uri::At(at_uri) => at_uri.as_str(),
103
+
Uri::Https(url) => url.as_str(),
104
+
Uri::Wss(url) => url.as_str(),
105
+
Uri::Cid(cid) => cid.as_str(),
106
+
Uri::Any(s) => s.as_ref(),
107
+
}
108
+
}
109
+
}
110
+
111
+
impl IntoStatic for Uri<'_> {
112
+
type Output = Uri<'static>;
113
+
114
+
fn into_static(self) -> Self::Output {
115
+
match self {
116
+
Uri::Did(did) => Uri::Did(did.into_static()),
117
+
Uri::At(at_uri) => Uri::At(AtUri::new_owned(at_uri.as_str()).unwrap()),
118
+
Uri::Https(url) => Uri::Https(url),
119
+
Uri::Wss(url) => Uri::Wss(url),
120
+
Uri::Cid(cid) => Uri::Cid(cid.into_static()),
121
+
Uri::Any(s) => Uri::Any(s.into_static()),
122
+
}
123
+
}
124
+
}
+25
crates/jacquard-common/src/types/value.rs
+25
crates/jacquard-common/src/types/value.rs
···
1
+
use bytes::Bytes;
2
+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
3
+
use smol_str::SmolStr;
4
+
use std::collections::BTreeMap;
5
+
6
+
use crate::types::{blob::Blob, string::*};
7
+
8
+
#[derive(Debug, Clone, PartialEq, Eq)]
9
+
pub enum Data<'s> {
10
+
Null,
11
+
Boolean(bool),
12
+
Integer(i64),
13
+
String(AtprotoStr<'s>),
14
+
Bytes(Bytes), // maybe need custom type for serialization
15
+
CidLink(Cid<'s>), // maybe need custom type for serialization
16
+
Array(Array<'s>),
17
+
Object(Object<'s>),
18
+
Blob(Blob<'s>),
19
+
}
20
+
21
+
#[derive(Debug, Clone, PartialEq, Eq)]
22
+
pub struct Array<'s>(pub Vec<Data<'s>>);
23
+
24
+
#[derive(Debug, Clone, PartialEq, Eq)]
25
+
pub struct Object<'s>(pub BTreeMap<SmolStr, Data<'s>>);
+14
crates/jacquard-lexicon/Cargo.toml
+14
crates/jacquard-lexicon/Cargo.toml
···
1
+
[package]
2
+
name = "jacquard-lexicon"
3
+
edition.workspace = true
4
+
version.workspace = true
5
+
authors.workspace = true
6
+
repository.workspace = true
7
+
keywords.workspace = true
8
+
categories.workspace = true
9
+
readme.workspace = true
10
+
documentation.workspace = true
11
+
exclude.workspace = true
12
+
description.workspace = true
13
+
14
+
[dependencies]