A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

wasm infra

Trezy a634edcc 2d9cc017

+2753 -22
+1389 -22
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "addr2line" 7 + version = "0.24.2" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 + dependencies = [ 11 + "gimli", 12 + ] 13 + 14 + [[package]] 6 15 name = "adler2" 7 16 version = "2.0.1" 8 17 source = "registry+https://github.com/rust-lang/crates.io-index" 9 18 checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 10 19 11 20 [[package]] 21 + name = "aead" 22 + version = "0.5.2" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 25 + dependencies = [ 26 + "crypto-common", 27 + "generic-array", 28 + ] 29 + 30 + [[package]] 31 + name = "aes" 32 + version = "0.8.4" 33 + source = "registry+https://github.com/rust-lang/crates.io-index" 34 + checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 35 + dependencies = [ 36 + "cfg-if", 37 + "cipher", 38 + "cpufeatures", 39 + ] 40 + 41 + [[package]] 42 + name = "aes-gcm" 43 + version = "0.10.3" 44 + source = "registry+https://github.com/rust-lang/crates.io-index" 45 + checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" 46 + dependencies = [ 47 + "aead", 48 + "aes", 49 + "cipher", 50 + "ctr", 51 + "ghash", 52 + "subtle", 53 + ] 54 + 55 + [[package]] 56 + name = "ahash" 57 + version = "0.8.12" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 60 + dependencies = [ 61 + "cfg-if", 62 + "once_cell", 63 + "version_check", 64 + "zerocopy", 65 + ] 66 + 67 + [[package]] 12 68 name = "aho-corasick" 13 69 version = "1.1.4" 14 70 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 24 80 checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 25 81 26 82 [[package]] 83 + name = "ambient-authority" 84 + version = "0.0.2" 85 + source = "registry+https://github.com/rust-lang/crates.io-index" 86 + checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" 87 + 88 + [[package]] 27 89 name = "android_system_properties" 28 90 version = "0.1.5" 29 91 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 39 101 checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" 40 102 41 103 [[package]] 104 + name = "ar_archive_writer" 105 + version = "0.5.1" 106 + source = "registry+https://github.com/rust-lang/crates.io-index" 107 + checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" 108 + dependencies = [ 109 + "object 0.37.3", 110 + ] 111 + 112 + [[package]] 113 + name = "arbitrary" 114 + version = "1.4.2" 115 + source = "registry+https://github.com/rust-lang/crates.io-index" 116 + checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" 117 + 118 + [[package]] 42 119 name = "arc-swap" 43 120 version = "1.8.2" 44 121 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 168 245 "atrium-common", 169 246 "atrium-identity", 170 247 "atrium-xrpc", 171 - "base64", 248 + "base64 0.22.1", 172 249 "chrono", 173 250 "dashmap", 174 251 "ecdsa", ··· 332 409 333 410 [[package]] 334 411 name = "base64" 412 + version = "0.21.7" 413 + source = "registry+https://github.com/rust-lang/crates.io-index" 414 + checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 415 + 416 + [[package]] 417 + name = "base64" 335 418 version = "0.22.1" 336 419 source = "registry+https://github.com/rust-lang/crates.io-index" 337 420 checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" ··· 375 458 version = "3.19.1" 376 459 source = "registry+https://github.com/rust-lang/crates.io-index" 377 460 checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" 461 + dependencies = [ 462 + "allocator-api2", 463 + ] 378 464 379 465 [[package]] 380 466 name = "byteorder" ··· 389 475 checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" 390 476 391 477 [[package]] 478 + name = "cap-fs-ext" 479 + version = "3.4.5" 480 + source = "registry+https://github.com/rust-lang/crates.io-index" 481 + checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" 482 + dependencies = [ 483 + "cap-primitives", 484 + "cap-std", 485 + "io-lifetimes", 486 + "windows-sys 0.52.0", 487 + ] 488 + 489 + [[package]] 490 + name = "cap-net-ext" 491 + version = "3.4.5" 492 + source = "registry+https://github.com/rust-lang/crates.io-index" 493 + checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" 494 + dependencies = [ 495 + "cap-primitives", 496 + "cap-std", 497 + "rustix 1.1.3", 498 + "smallvec", 499 + ] 500 + 501 + [[package]] 502 + name = "cap-primitives" 503 + version = "3.4.5" 504 + source = "registry+https://github.com/rust-lang/crates.io-index" 505 + checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" 506 + dependencies = [ 507 + "ambient-authority", 508 + "fs-set-times", 509 + "io-extras", 510 + "io-lifetimes", 511 + "ipnet", 512 + "maybe-owned", 513 + "rustix 1.1.3", 514 + "rustix-linux-procfs", 515 + "windows-sys 0.52.0", 516 + "winx", 517 + ] 518 + 519 + [[package]] 520 + name = "cap-rand" 521 + version = "3.4.5" 522 + source = "registry+https://github.com/rust-lang/crates.io-index" 523 + checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" 524 + dependencies = [ 525 + "ambient-authority", 526 + "rand 0.8.5", 527 + ] 528 + 529 + [[package]] 530 + name = "cap-std" 531 + version = "3.4.5" 532 + source = "registry+https://github.com/rust-lang/crates.io-index" 533 + checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" 534 + dependencies = [ 535 + "cap-primitives", 536 + "io-extras", 537 + "io-lifetimes", 538 + "rustix 1.1.3", 539 + ] 540 + 541 + [[package]] 542 + name = "cap-time-ext" 543 + version = "3.4.5" 544 + source = "registry+https://github.com/rust-lang/crates.io-index" 545 + checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" 546 + dependencies = [ 547 + "ambient-authority", 548 + "cap-primitives", 549 + "iana-time-zone", 550 + "once_cell", 551 + "rustix 1.1.3", 552 + "winx", 553 + ] 554 + 555 + [[package]] 392 556 name = "cc" 393 557 version = "1.2.55" 394 558 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 462 626 ] 463 627 464 628 [[package]] 629 + name = "cipher" 630 + version = "0.4.4" 631 + source = "registry+https://github.com/rust-lang/crates.io-index" 632 + checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 633 + dependencies = [ 634 + "crypto-common", 635 + "inout", 636 + ] 637 + 638 + [[package]] 465 639 name = "cmake" 466 640 version = "0.1.57" 467 641 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 471 645 ] 472 646 473 647 [[package]] 648 + name = "cobs" 649 + version = "0.3.0" 650 + source = "registry+https://github.com/rust-lang/crates.io-index" 651 + checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" 652 + dependencies = [ 653 + "thiserror 2.0.18", 654 + ] 655 + 656 + [[package]] 474 657 name = "compression-codecs" 475 658 version = "0.4.37" 476 659 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 514 697 source = "registry+https://github.com/rust-lang/crates.io-index" 515 698 checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" 516 699 dependencies = [ 517 - "base64", 700 + "base64 0.22.1", 518 701 "hkdf", 519 702 "hmac", 520 703 "percent-encoding", ··· 551 734 ] 552 735 553 736 [[package]] 737 + name = "cpp_demangle" 738 + version = "0.4.5" 739 + source = "registry+https://github.com/rust-lang/crates.io-index" 740 + checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" 741 + dependencies = [ 742 + "cfg-if", 743 + ] 744 + 745 + [[package]] 554 746 name = "cpufeatures" 555 747 version = "0.2.17" 556 748 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 560 752 ] 561 753 562 754 [[package]] 755 + name = "cranelift-bforest" 756 + version = "0.116.1" 757 + source = "registry+https://github.com/rust-lang/crates.io-index" 758 + checksum = "e15d04a0ce86cb36ead88ad68cf693ffd6cda47052b9e0ac114bc47fd9cd23c4" 759 + dependencies = [ 760 + "cranelift-entity", 761 + ] 762 + 763 + [[package]] 764 + name = "cranelift-bitset" 765 + version = "0.116.1" 766 + source = "registry+https://github.com/rust-lang/crates.io-index" 767 + checksum = "7c6e3969a7ce267259ce244b7867c5d3bc9e65b0a87e81039588dfdeaede9f34" 768 + dependencies = [ 769 + "serde", 770 + "serde_derive", 771 + ] 772 + 773 + [[package]] 774 + name = "cranelift-codegen" 775 + version = "0.116.1" 776 + source = "registry+https://github.com/rust-lang/crates.io-index" 777 + checksum = "2c22032c4cb42558371cf516bb47f26cdad1819d3475c133e93c49f50ebf304e" 778 + dependencies = [ 779 + "bumpalo", 780 + "cranelift-bforest", 781 + "cranelift-bitset", 782 + "cranelift-codegen-meta", 783 + "cranelift-codegen-shared", 784 + "cranelift-control", 785 + "cranelift-entity", 786 + "cranelift-isle", 787 + "gimli", 788 + "hashbrown 0.14.5", 789 + "log", 790 + "regalloc2", 791 + "rustc-hash", 792 + "serde", 793 + "smallvec", 794 + "target-lexicon", 795 + ] 796 + 797 + [[package]] 798 + name = "cranelift-codegen-meta" 799 + version = "0.116.1" 800 + source = "registry+https://github.com/rust-lang/crates.io-index" 801 + checksum = "c904bc71c61b27fc57827f4a1379f29de64fe95653b620a3db77d59655eee0b8" 802 + dependencies = [ 803 + "cranelift-codegen-shared", 804 + ] 805 + 806 + [[package]] 807 + name = "cranelift-codegen-shared" 808 + version = "0.116.1" 809 + source = "registry+https://github.com/rust-lang/crates.io-index" 810 + checksum = "40180f5497572f644ce88c255480981ae2ec1d7bb4d8e0c0136a13b87a2f2ceb" 811 + 812 + [[package]] 813 + name = "cranelift-control" 814 + version = "0.116.1" 815 + source = "registry+https://github.com/rust-lang/crates.io-index" 816 + checksum = "26d132c6d0bd8a489563472afc171759da0707804a65ece7ceb15a8c6d7dd5ef" 817 + dependencies = [ 818 + "arbitrary", 819 + ] 820 + 821 + [[package]] 822 + name = "cranelift-entity" 823 + version = "0.116.1" 824 + source = "registry+https://github.com/rust-lang/crates.io-index" 825 + checksum = "4b2d0d9618275474fbf679dd018ac6e009acbd6ae6850f6a67be33fb3b00b323" 826 + dependencies = [ 827 + "cranelift-bitset", 828 + "serde", 829 + "serde_derive", 830 + ] 831 + 832 + [[package]] 833 + name = "cranelift-frontend" 834 + version = "0.116.1" 835 + source = "registry+https://github.com/rust-lang/crates.io-index" 836 + checksum = "4fac41e16729107393174b0c9e3730fb072866100e1e64e80a1a963b2e484d57" 837 + dependencies = [ 838 + "cranelift-codegen", 839 + "log", 840 + "smallvec", 841 + "target-lexicon", 842 + ] 843 + 844 + [[package]] 845 + name = "cranelift-isle" 846 + version = "0.116.1" 847 + source = "registry+https://github.com/rust-lang/crates.io-index" 848 + checksum = "1ca20d576e5070044d0a72a9effc2deacf4d6aa650403189d8ea50126483944d" 849 + 850 + [[package]] 851 + name = "cranelift-native" 852 + version = "0.116.1" 853 + source = "registry+https://github.com/rust-lang/crates.io-index" 854 + checksum = "b8dee82f3f1f2c4cba9177f1cc5e350fe98764379bcd29340caa7b01f85076c7" 855 + dependencies = [ 856 + "cranelift-codegen", 857 + "libc", 858 + "target-lexicon", 859 + ] 860 + 861 + [[package]] 563 862 name = "crc" 564 863 version = "3.4.0" 565 864 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 599 898 ] 600 899 601 900 [[package]] 901 + name = "crossbeam-deque" 902 + version = "0.8.6" 903 + source = "registry+https://github.com/rust-lang/crates.io-index" 904 + checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 905 + dependencies = [ 906 + "crossbeam-epoch", 907 + "crossbeam-utils", 908 + ] 909 + 910 + [[package]] 602 911 name = "crossbeam-epoch" 603 912 version = "0.9.18" 604 913 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 647 956 checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 648 957 dependencies = [ 649 958 "generic-array", 959 + "rand_core 0.6.4", 650 960 "typenum", 651 961 ] 652 962 653 963 [[package]] 964 + name = "ctr" 965 + version = "0.9.2" 966 + source = "registry+https://github.com/rust-lang/crates.io-index" 967 + checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" 968 + dependencies = [ 969 + "cipher", 970 + ] 971 + 972 + [[package]] 654 973 name = "dashmap" 655 974 version = "6.1.0" 656 975 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 709 1028 checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" 710 1029 711 1030 [[package]] 1031 + name = "debugid" 1032 + version = "0.8.0" 1033 + source = "registry+https://github.com/rust-lang/crates.io-index" 1034 + checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" 1035 + dependencies = [ 1036 + "uuid", 1037 + ] 1038 + 1039 + [[package]] 712 1040 name = "der" 713 1041 version = "0.7.10" 714 1042 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 741 1069 ] 742 1070 743 1071 [[package]] 1072 + name = "directories-next" 1073 + version = "2.0.0" 1074 + source = "registry+https://github.com/rust-lang/crates.io-index" 1075 + checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" 1076 + dependencies = [ 1077 + "cfg-if", 1078 + "dirs-sys-next", 1079 + ] 1080 + 1081 + [[package]] 1082 + name = "dirs" 1083 + version = "4.0.0" 1084 + source = "registry+https://github.com/rust-lang/crates.io-index" 1085 + checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 1086 + dependencies = [ 1087 + "dirs-sys", 1088 + ] 1089 + 1090 + [[package]] 1091 + name = "dirs-sys" 1092 + version = "0.3.7" 1093 + source = "registry+https://github.com/rust-lang/crates.io-index" 1094 + checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 1095 + dependencies = [ 1096 + "libc", 1097 + "redox_users", 1098 + "winapi", 1099 + ] 1100 + 1101 + [[package]] 1102 + name = "dirs-sys-next" 1103 + version = "0.1.2" 1104 + source = "registry+https://github.com/rust-lang/crates.io-index" 1105 + checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 1106 + dependencies = [ 1107 + "libc", 1108 + "redox_users", 1109 + "winapi", 1110 + ] 1111 + 1112 + [[package]] 744 1113 name = "displaydoc" 745 1114 version = "0.2.5" 746 1115 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 807 1176 ] 808 1177 809 1178 [[package]] 1179 + name = "embedded-io" 1180 + version = "0.4.0" 1181 + source = "registry+https://github.com/rust-lang/crates.io-index" 1182 + checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" 1183 + 1184 + [[package]] 1185 + name = "embedded-io" 1186 + version = "0.6.1" 1187 + source = "registry+https://github.com/rust-lang/crates.io-index" 1188 + checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 1189 + 1190 + [[package]] 810 1191 name = "encoding_rs" 811 1192 version = "0.8.35" 812 1193 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 893 1274 ] 894 1275 895 1276 [[package]] 1277 + name = "fallible-iterator" 1278 + version = "0.3.0" 1279 + source = "registry+https://github.com/rust-lang/crates.io-index" 1280 + checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 1281 + 1282 + [[package]] 896 1283 name = "fastrand" 897 1284 version = "2.3.0" 898 1285 source = "registry+https://github.com/rust-lang/crates.io-index" 899 1286 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 900 1287 901 1288 [[package]] 1289 + name = "fd-lock" 1290 + version = "4.0.4" 1291 + source = "registry+https://github.com/rust-lang/crates.io-index" 1292 + checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" 1293 + dependencies = [ 1294 + "cfg-if", 1295 + "rustix 1.1.3", 1296 + "windows-sys 0.52.0", 1297 + ] 1298 + 1299 + [[package]] 902 1300 name = "ff" 903 1301 version = "0.13.1" 904 1302 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 969 1367 checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 970 1368 dependencies = [ 971 1369 "percent-encoding", 1370 + ] 1371 + 1372 + [[package]] 1373 + name = "fs-set-times" 1374 + version = "0.20.3" 1375 + source = "registry+https://github.com/rust-lang/crates.io-index" 1376 + checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" 1377 + dependencies = [ 1378 + "io-lifetimes", 1379 + "rustix 1.1.3", 1380 + "windows-sys 0.52.0", 972 1381 ] 973 1382 974 1383 [[package]] ··· 1078 1487 ] 1079 1488 1080 1489 [[package]] 1490 + name = "fxhash" 1491 + version = "0.2.1" 1492 + source = "registry+https://github.com/rust-lang/crates.io-index" 1493 + checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 1494 + dependencies = [ 1495 + "byteorder", 1496 + ] 1497 + 1498 + [[package]] 1499 + name = "fxprof-processed-profile" 1500 + version = "0.6.0" 1501 + source = "registry+https://github.com/rust-lang/crates.io-index" 1502 + checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" 1503 + dependencies = [ 1504 + "bitflags", 1505 + "debugid", 1506 + "fxhash", 1507 + "serde", 1508 + "serde_json", 1509 + ] 1510 + 1511 + [[package]] 1081 1512 name = "generic-array" 1082 1513 version = "0.14.7" 1083 1514 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1127 1558 ] 1128 1559 1129 1560 [[package]] 1561 + name = "ghash" 1562 + version = "0.5.1" 1563 + source = "registry+https://github.com/rust-lang/crates.io-index" 1564 + checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" 1565 + dependencies = [ 1566 + "opaque-debug", 1567 + "polyval", 1568 + ] 1569 + 1570 + [[package]] 1571 + name = "gimli" 1572 + version = "0.31.1" 1573 + source = "registry+https://github.com/rust-lang/crates.io-index" 1574 + checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 1575 + dependencies = [ 1576 + "fallible-iterator", 1577 + "indexmap", 1578 + "stable_deref_trait", 1579 + ] 1580 + 1581 + [[package]] 1130 1582 name = "group" 1131 1583 version = "0.13.0" 1132 1584 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1171 1623 name = "happyview" 1172 1624 version = "0.1.0" 1173 1625 dependencies = [ 1626 + "aes-gcm", 1627 + "anyhow", 1174 1628 "arc-swap", 1175 1629 "atrium-api", 1176 1630 "atrium-common", ··· 1179 1633 "atrium-xrpc", 1180 1634 "axum", 1181 1635 "axum-extra", 1182 - "base64", 1636 + "base64 0.22.1", 1183 1637 "bytes", 1184 1638 "chrono", 1185 1639 "ciborium", ··· 1205 1659 "serial_test", 1206 1660 "sha2", 1207 1661 "sqlx", 1662 + "thiserror 2.0.18", 1208 1663 "tokio", 1209 1664 "tokio-rustls", 1210 1665 "tokio-tungstenite", ··· 1214 1669 "tracing-subscriber", 1215 1670 "urlencoding", 1216 1671 "uuid", 1672 + "wasmtime", 1673 + "wasmtime-wasi", 1217 1674 "webpki-roots 0.26.11", 1218 1675 "wiremock", 1219 1676 ] ··· 1223 1680 version = "0.14.5" 1224 1681 source = "registry+https://github.com/rust-lang/crates.io-index" 1225 1682 checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1683 + dependencies = [ 1684 + "ahash", 1685 + ] 1226 1686 1227 1687 [[package]] 1228 1688 name = "hashbrown" ··· 1233 1693 "allocator-api2", 1234 1694 "equivalent", 1235 1695 "foldhash", 1696 + "serde", 1236 1697 ] 1237 1698 1238 1699 [[package]] ··· 1453 1914 source = "registry+https://github.com/rust-lang/crates.io-index" 1454 1915 checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" 1455 1916 dependencies = [ 1456 - "base64", 1917 + "base64 0.22.1", 1457 1918 "bytes", 1458 1919 "futures-channel", 1459 1920 "futures-util", ··· 1617 2078 ] 1618 2079 1619 2080 [[package]] 2081 + name = "inout" 2082 + version = "0.1.4" 2083 + source = "registry+https://github.com/rust-lang/crates.io-index" 2084 + checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 2085 + dependencies = [ 2086 + "generic-array", 2087 + ] 2088 + 2089 + [[package]] 2090 + name = "io-extras" 2091 + version = "0.18.4" 2092 + source = "registry+https://github.com/rust-lang/crates.io-index" 2093 + checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" 2094 + dependencies = [ 2095 + "io-lifetimes", 2096 + "windows-sys 0.52.0", 2097 + ] 2098 + 2099 + [[package]] 2100 + name = "io-lifetimes" 2101 + version = "2.0.4" 2102 + source = "registry+https://github.com/rust-lang/crates.io-index" 2103 + checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" 2104 + 2105 + [[package]] 1620 2106 name = "ipconfig" 1621 2107 version = "0.3.2" 1622 2108 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1656 2142 ] 1657 2143 1658 2144 [[package]] 2145 + name = "itertools" 2146 + version = "0.12.1" 2147 + source = "registry+https://github.com/rust-lang/crates.io-index" 2148 + checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 2149 + dependencies = [ 2150 + "either", 2151 + ] 2152 + 2153 + [[package]] 1659 2154 name = "itoa" 1660 2155 version = "1.0.17" 1661 2156 source = "registry+https://github.com/rust-lang/crates.io-index" 1662 2157 checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" 1663 2158 1664 2159 [[package]] 2160 + name = "ittapi" 2161 + version = "0.4.0" 2162 + source = "registry+https://github.com/rust-lang/crates.io-index" 2163 + checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" 2164 + dependencies = [ 2165 + "anyhow", 2166 + "ittapi-sys", 2167 + "log", 2168 + ] 2169 + 2170 + [[package]] 2171 + name = "ittapi-sys" 2172 + version = "0.4.0" 2173 + source = "registry+https://github.com/rust-lang/crates.io-index" 2174 + checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" 2175 + dependencies = [ 2176 + "cc", 2177 + ] 2178 + 2179 + [[package]] 1665 2180 name = "jobserver" 1666 2181 version = "0.1.34" 1667 2182 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1721 2236 source = "registry+https://github.com/rust-lang/crates.io-index" 1722 2237 checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" 1723 2238 dependencies = [ 1724 - "base64", 2239 + "base64 0.22.1", 1725 2240 "js-sys", 1726 2241 "pem", 1727 2242 "ring", ··· 1763 2278 ] 1764 2279 1765 2280 [[package]] 2281 + name = "leb128" 2282 + version = "0.2.5" 2283 + source = "registry+https://github.com/rust-lang/crates.io-index" 2284 + checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 2285 + 2286 + [[package]] 1766 2287 name = "leb128fmt" 1767 2288 version = "0.1.0" 1768 2289 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1801 2322 "pkg-config", 1802 2323 "vcpkg", 1803 2324 ] 2325 + 2326 + [[package]] 2327 + name = "linux-raw-sys" 2328 + version = "0.4.15" 2329 + source = "registry+https://github.com/rust-lang/crates.io-index" 2330 + checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1804 2331 1805 2332 [[package]] 1806 2333 name = "linux-raw-sys" ··· 1855 2382 dependencies = [ 1856 2383 "cc", 1857 2384 "which", 2385 + ] 2386 + 2387 + [[package]] 2388 + name = "mach2" 2389 + version = "0.4.3" 2390 + source = "registry+https://github.com/rust-lang/crates.io-index" 2391 + checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" 2392 + dependencies = [ 2393 + "libc", 1858 2394 ] 1859 2395 1860 2396 [[package]] ··· 1884 2420 checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 1885 2421 1886 2422 [[package]] 2423 + name = "maybe-owned" 2424 + version = "0.3.4" 2425 + source = "registry+https://github.com/rust-lang/crates.io-index" 2426 + checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" 2427 + 2428 + [[package]] 1887 2429 name = "md-5" 1888 2430 version = "0.10.6" 1889 2431 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1898 2440 version = "2.8.0" 1899 2441 source = "registry+https://github.com/rust-lang/crates.io-index" 1900 2442 checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 2443 + 2444 + [[package]] 2445 + name = "memfd" 2446 + version = "0.6.5" 2447 + source = "registry+https://github.com/rust-lang/crates.io-index" 2448 + checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" 2449 + dependencies = [ 2450 + "rustix 1.1.3", 2451 + ] 1901 2452 1902 2453 [[package]] 1903 2454 name = "mime" ··· 2129 2680 ] 2130 2681 2131 2682 [[package]] 2683 + name = "object" 2684 + version = "0.36.7" 2685 + source = "registry+https://github.com/rust-lang/crates.io-index" 2686 + checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 2687 + dependencies = [ 2688 + "crc32fast", 2689 + "hashbrown 0.15.5", 2690 + "indexmap", 2691 + "memchr", 2692 + ] 2693 + 2694 + [[package]] 2695 + name = "object" 2696 + version = "0.37.3" 2697 + source = "registry+https://github.com/rust-lang/crates.io-index" 2698 + checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" 2699 + dependencies = [ 2700 + "memchr", 2701 + ] 2702 + 2703 + [[package]] 2132 2704 name = "once_cell" 2133 2705 version = "1.21.3" 2134 2706 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2137 2709 "critical-section", 2138 2710 "portable-atomic", 2139 2711 ] 2712 + 2713 + [[package]] 2714 + name = "opaque-debug" 2715 + version = "0.3.1" 2716 + source = "registry+https://github.com/rust-lang/crates.io-index" 2717 + checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 2140 2718 2141 2719 [[package]] 2142 2720 name = "openssl" ··· 2233 2811 ] 2234 2812 2235 2813 [[package]] 2814 + name = "paste" 2815 + version = "1.0.15" 2816 + source = "registry+https://github.com/rust-lang/crates.io-index" 2817 + checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 2818 + 2819 + [[package]] 2236 2820 name = "pem" 2237 2821 version = "3.0.6" 2238 2822 source = "registry+https://github.com/rust-lang/crates.io-index" 2239 2823 checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" 2240 2824 dependencies = [ 2241 - "base64", 2825 + "base64 0.22.1", 2242 2826 "serde_core", 2243 2827 ] 2244 2828 ··· 2297 2881 checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 2298 2882 2299 2883 [[package]] 2884 + name = "polyval" 2885 + version = "0.6.2" 2886 + source = "registry+https://github.com/rust-lang/crates.io-index" 2887 + checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" 2888 + dependencies = [ 2889 + "cfg-if", 2890 + "cpufeatures", 2891 + "opaque-debug", 2892 + "universal-hash", 2893 + ] 2894 + 2895 + [[package]] 2300 2896 name = "portable-atomic" 2301 2897 version = "1.13.1" 2302 2898 source = "registry+https://github.com/rust-lang/crates.io-index" 2303 2899 checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" 2304 2900 2305 2901 [[package]] 2902 + name = "postcard" 2903 + version = "1.1.3" 2904 + source = "registry+https://github.com/rust-lang/crates.io-index" 2905 + checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" 2906 + dependencies = [ 2907 + "cobs", 2908 + "embedded-io 0.4.0", 2909 + "embedded-io 0.6.1", 2910 + "serde", 2911 + ] 2912 + 2913 + [[package]] 2306 2914 name = "potential_utf" 2307 2915 version = "0.1.4" 2308 2916 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2355 2963 ] 2356 2964 2357 2965 [[package]] 2966 + name = "psm" 2967 + version = "0.1.30" 2968 + source = "registry+https://github.com/rust-lang/crates.io-index" 2969 + checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8" 2970 + dependencies = [ 2971 + "ar_archive_writer", 2972 + "cc", 2973 + ] 2974 + 2975 + [[package]] 2976 + name = "pulley-interpreter" 2977 + version = "29.0.1" 2978 + source = "registry+https://github.com/rust-lang/crates.io-index" 2979 + checksum = "62d95f8575df49a2708398182f49a888cf9dc30210fb1fd2df87c889edcee75d" 2980 + dependencies = [ 2981 + "cranelift-bitset", 2982 + "log", 2983 + "sptr", 2984 + "wasmtime-math", 2985 + ] 2986 + 2987 + [[package]] 2358 2988 name = "quote" 2359 2989 version = "1.0.44" 2360 2990 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2426 3056 checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" 2427 3057 dependencies = [ 2428 3058 "getrandom 0.3.4", 3059 + ] 3060 + 3061 + [[package]] 3062 + name = "rayon" 3063 + version = "1.11.0" 3064 + source = "registry+https://github.com/rust-lang/crates.io-index" 3065 + checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" 3066 + dependencies = [ 3067 + "either", 3068 + "rayon-core", 3069 + ] 3070 + 3071 + [[package]] 3072 + name = "rayon-core" 3073 + version = "1.13.0" 3074 + source = "registry+https://github.com/rust-lang/crates.io-index" 3075 + checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" 3076 + dependencies = [ 3077 + "crossbeam-deque", 3078 + "crossbeam-utils", 2429 3079 ] 2430 3080 2431 3081 [[package]] ··· 2447 3097 ] 2448 3098 2449 3099 [[package]] 3100 + name = "redox_users" 3101 + version = "0.4.6" 3102 + source = "registry+https://github.com/rust-lang/crates.io-index" 3103 + checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 3104 + dependencies = [ 3105 + "getrandom 0.2.17", 3106 + "libredox", 3107 + "thiserror 1.0.69", 3108 + ] 3109 + 3110 + [[package]] 3111 + name = "regalloc2" 3112 + version = "0.11.2" 3113 + source = "registry+https://github.com/rust-lang/crates.io-index" 3114 + checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a" 3115 + dependencies = [ 3116 + "allocator-api2", 3117 + "bumpalo", 3118 + "hashbrown 0.15.5", 3119 + "log", 3120 + "rustc-hash", 3121 + "smallvec", 3122 + ] 3123 + 3124 + [[package]] 2450 3125 name = "regex" 2451 3126 version = "1.12.3" 2452 3127 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2481 3156 source = "registry+https://github.com/rust-lang/crates.io-index" 2482 3157 checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" 2483 3158 dependencies = [ 2484 - "base64", 3159 + "base64 0.22.1", 2485 3160 "bytes", 2486 3161 "encoding_rs", 2487 3162 "futures-core", ··· 2566 3241 ] 2567 3242 2568 3243 [[package]] 3244 + name = "rustc-demangle" 3245 + version = "0.1.27" 3246 + source = "registry+https://github.com/rust-lang/crates.io-index" 3247 + checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" 3248 + 3249 + [[package]] 2569 3250 name = "rustc-hash" 2570 3251 version = "2.1.1" 2571 3252 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2573 3254 2574 3255 [[package]] 2575 3256 name = "rustix" 3257 + version = "0.38.44" 3258 + source = "registry+https://github.com/rust-lang/crates.io-index" 3259 + checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 3260 + dependencies = [ 3261 + "bitflags", 3262 + "errno", 3263 + "libc", 3264 + "linux-raw-sys 0.4.15", 3265 + "windows-sys 0.52.0", 3266 + ] 3267 + 3268 + [[package]] 3269 + name = "rustix" 2576 3270 version = "1.1.3" 2577 3271 source = "registry+https://github.com/rust-lang/crates.io-index" 2578 3272 checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" ··· 2580 3274 "bitflags", 2581 3275 "errno", 2582 3276 "libc", 2583 - "linux-raw-sys", 3277 + "linux-raw-sys 0.11.0", 2584 3278 "windows-sys 0.61.2", 3279 + ] 3280 + 3281 + [[package]] 3282 + name = "rustix-linux-procfs" 3283 + version = "0.1.1" 3284 + source = "registry+https://github.com/rust-lang/crates.io-index" 3285 + checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" 3286 + dependencies = [ 3287 + "once_cell", 3288 + "rustix 1.1.3", 2585 3289 ] 2586 3290 2587 3291 [[package]] ··· 2705 3409 version = "1.0.27" 2706 3410 source = "registry+https://github.com/rust-lang/crates.io-index" 2707 3411 checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 3412 + dependencies = [ 3413 + "serde", 3414 + "serde_core", 3415 + ] 2708 3416 2709 3417 [[package]] 2710 3418 name = "serde" ··· 2794 3502 ] 2795 3503 2796 3504 [[package]] 3505 + name = "serde_spanned" 3506 + version = "0.6.9" 3507 + source = "registry+https://github.com/rust-lang/crates.io-index" 3508 + checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" 3509 + dependencies = [ 3510 + "serde", 3511 + ] 3512 + 3513 + [[package]] 2797 3514 name = "serde_urlencoded" 2798 3515 version = "0.7.1" 2799 3516 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2863 3580 ] 2864 3581 2865 3582 [[package]] 3583 + name = "shellexpand" 3584 + version = "2.1.2" 3585 + source = "registry+https://github.com/rust-lang/crates.io-index" 3586 + checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" 3587 + dependencies = [ 3588 + "dirs", 3589 + ] 3590 + 3591 + [[package]] 2866 3592 name = "shlex" 2867 3593 version = "1.3.0" 2868 3594 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2961 3687 ] 2962 3688 2963 3689 [[package]] 3690 + name = "sptr" 3691 + version = "0.3.2" 3692 + source = "registry+https://github.com/rust-lang/crates.io-index" 3693 + checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" 3694 + 3695 + [[package]] 2964 3696 name = "sqlx" 2965 3697 version = "0.8.6" 2966 3698 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2979 3711 source = "registry+https://github.com/rust-lang/crates.io-index" 2980 3712 checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" 2981 3713 dependencies = [ 2982 - "base64", 3714 + "base64 0.22.1", 2983 3715 "bytes", 2984 3716 "chrono", 2985 3717 "crc", ··· 3055 3787 checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" 3056 3788 dependencies = [ 3057 3789 "atoi", 3058 - "base64", 3790 + "base64 0.22.1", 3059 3791 "bitflags", 3060 3792 "byteorder", 3061 3793 "bytes", ··· 3098 3830 checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" 3099 3831 dependencies = [ 3100 3832 "atoi", 3101 - "base64", 3833 + "base64 0.22.1", 3102 3834 "bitflags", 3103 3835 "byteorder", 3104 3836 "chrono", ··· 3230 3962 ] 3231 3963 3232 3964 [[package]] 3965 + name = "system-interface" 3966 + version = "0.27.3" 3967 + source = "registry+https://github.com/rust-lang/crates.io-index" 3968 + checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" 3969 + dependencies = [ 3970 + "bitflags", 3971 + "cap-fs-ext", 3972 + "cap-std", 3973 + "fd-lock", 3974 + "io-lifetimes", 3975 + "rustix 0.38.44", 3976 + "windows-sys 0.52.0", 3977 + "winx", 3978 + ] 3979 + 3980 + [[package]] 3233 3981 name = "tagptr" 3234 3982 version = "0.2.0" 3235 3983 source = "registry+https://github.com/rust-lang/crates.io-index" 3236 3984 checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 3237 3985 3238 3986 [[package]] 3987 + name = "target-lexicon" 3988 + version = "0.13.5" 3989 + source = "registry+https://github.com/rust-lang/crates.io-index" 3990 + checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" 3991 + 3992 + [[package]] 3239 3993 name = "tempfile" 3240 3994 version = "3.25.0" 3241 3995 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3244 3998 "fastrand", 3245 3999 "getrandom 0.4.1", 3246 4000 "once_cell", 3247 - "rustix", 4001 + "rustix 1.1.3", 3248 4002 "windows-sys 0.61.2", 4003 + ] 4004 + 4005 + [[package]] 4006 + name = "termcolor" 4007 + version = "1.4.1" 4008 + source = "registry+https://github.com/rust-lang/crates.io-index" 4009 + checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 4010 + dependencies = [ 4011 + "winapi-util", 3249 4012 ] 3250 4013 3251 4014 [[package]] ··· 3442 4205 ] 3443 4206 3444 4207 [[package]] 4208 + name = "toml" 4209 + version = "0.8.23" 4210 + source = "registry+https://github.com/rust-lang/crates.io-index" 4211 + checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 4212 + dependencies = [ 4213 + "serde", 4214 + "serde_spanned", 4215 + "toml_datetime", 4216 + "toml_edit", 4217 + ] 4218 + 4219 + [[package]] 4220 + name = "toml_datetime" 4221 + version = "0.6.11" 4222 + source = "registry+https://github.com/rust-lang/crates.io-index" 4223 + checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" 4224 + dependencies = [ 4225 + "serde", 4226 + ] 4227 + 4228 + [[package]] 4229 + name = "toml_edit" 4230 + version = "0.22.27" 4231 + source = "registry+https://github.com/rust-lang/crates.io-index" 4232 + checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" 4233 + dependencies = [ 4234 + "indexmap", 4235 + "serde", 4236 + "serde_spanned", 4237 + "toml_datetime", 4238 + "toml_write", 4239 + "winnow", 4240 + ] 4241 + 4242 + [[package]] 4243 + name = "toml_write" 4244 + version = "0.1.2" 4245 + source = "registry+https://github.com/rust-lang/crates.io-index" 4246 + checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" 4247 + 4248 + [[package]] 3445 4249 name = "tower" 3446 4250 version = "0.5.3" 3447 4251 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3655 4459 checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" 3656 4460 3657 4461 [[package]] 4462 + name = "unicode-width" 4463 + version = "0.2.2" 4464 + source = "registry+https://github.com/rust-lang/crates.io-index" 4465 + checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" 4466 + 4467 + [[package]] 3658 4468 name = "unicode-xid" 3659 4469 version = "0.2.6" 3660 4470 source = "registry+https://github.com/rust-lang/crates.io-index" 3661 4471 checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 3662 4472 3663 4473 [[package]] 4474 + name = "universal-hash" 4475 + version = "0.5.1" 4476 + source = "registry+https://github.com/rust-lang/crates.io-index" 4477 + checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 4478 + dependencies = [ 4479 + "crypto-common", 4480 + "subtle", 4481 + ] 4482 + 4483 + [[package]] 3664 4484 name = "unsigned-varint" 3665 4485 version = "0.8.0" 3666 4486 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3831 4651 3832 4652 [[package]] 3833 4653 name = "wasm-encoder" 4654 + version = "0.221.3" 4655 + source = "registry+https://github.com/rust-lang/crates.io-index" 4656 + checksum = "dc8444fe4920de80a4fe5ab564fff2ae58b6b73166b89751f8c6c93509da32e5" 4657 + dependencies = [ 4658 + "leb128", 4659 + "wasmparser 0.221.3", 4660 + ] 4661 + 4662 + [[package]] 4663 + name = "wasm-encoder" 3834 4664 version = "0.244.0" 3835 4665 source = "registry+https://github.com/rust-lang/crates.io-index" 3836 4666 checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" 3837 4667 dependencies = [ 3838 4668 "leb128fmt", 3839 - "wasmparser", 4669 + "wasmparser 0.244.0", 4670 + ] 4671 + 4672 + [[package]] 4673 + name = "wasm-encoder" 4674 + version = "0.245.1" 4675 + source = "registry+https://github.com/rust-lang/crates.io-index" 4676 + checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" 4677 + dependencies = [ 4678 + "leb128fmt", 4679 + "wasmparser 0.245.1", 3840 4680 ] 3841 4681 3842 4682 [[package]] ··· 3847 4687 dependencies = [ 3848 4688 "anyhow", 3849 4689 "indexmap", 3850 - "wasm-encoder", 3851 - "wasmparser", 4690 + "wasm-encoder 0.244.0", 4691 + "wasmparser 0.244.0", 4692 + ] 4693 + 4694 + [[package]] 4695 + name = "wasmparser" 4696 + version = "0.221.3" 4697 + source = "registry+https://github.com/rust-lang/crates.io-index" 4698 + checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" 4699 + dependencies = [ 4700 + "bitflags", 4701 + "hashbrown 0.15.5", 4702 + "indexmap", 4703 + "semver", 4704 + "serde", 3852 4705 ] 3853 4706 3854 4707 [[package]] ··· 3864 4717 ] 3865 4718 3866 4719 [[package]] 4720 + name = "wasmparser" 4721 + version = "0.245.1" 4722 + source = "registry+https://github.com/rust-lang/crates.io-index" 4723 + checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" 4724 + dependencies = [ 4725 + "bitflags", 4726 + "indexmap", 4727 + "semver", 4728 + ] 4729 + 4730 + [[package]] 4731 + name = "wasmprinter" 4732 + version = "0.221.3" 4733 + source = "registry+https://github.com/rust-lang/crates.io-index" 4734 + checksum = "7343c42a97f2926c7819ff81b64012092ae954c5d83ddd30c9fcdefd97d0b283" 4735 + dependencies = [ 4736 + "anyhow", 4737 + "termcolor", 4738 + "wasmparser 0.221.3", 4739 + ] 4740 + 4741 + [[package]] 4742 + name = "wasmtime" 4743 + version = "29.0.1" 4744 + source = "registry+https://github.com/rust-lang/crates.io-index" 4745 + checksum = "11976a250672556d1c4c04c6d5d7656ac9192ac9edc42a4587d6c21460010e69" 4746 + dependencies = [ 4747 + "addr2line", 4748 + "anyhow", 4749 + "async-trait", 4750 + "bitflags", 4751 + "bumpalo", 4752 + "cc", 4753 + "cfg-if", 4754 + "encoding_rs", 4755 + "fxprof-processed-profile", 4756 + "gimli", 4757 + "hashbrown 0.14.5", 4758 + "indexmap", 4759 + "ittapi", 4760 + "libc", 4761 + "log", 4762 + "mach2", 4763 + "memfd", 4764 + "object 0.36.7", 4765 + "once_cell", 4766 + "paste", 4767 + "postcard", 4768 + "psm", 4769 + "pulley-interpreter", 4770 + "rayon", 4771 + "rustix 0.38.44", 4772 + "semver", 4773 + "serde", 4774 + "serde_derive", 4775 + "serde_json", 4776 + "smallvec", 4777 + "sptr", 4778 + "target-lexicon", 4779 + "trait-variant", 4780 + "wasm-encoder 0.221.3", 4781 + "wasmparser 0.221.3", 4782 + "wasmtime-asm-macros", 4783 + "wasmtime-cache", 4784 + "wasmtime-component-macro", 4785 + "wasmtime-component-util", 4786 + "wasmtime-cranelift", 4787 + "wasmtime-environ", 4788 + "wasmtime-fiber", 4789 + "wasmtime-jit-debug", 4790 + "wasmtime-jit-icache-coherence", 4791 + "wasmtime-math", 4792 + "wasmtime-slab", 4793 + "wasmtime-versioned-export-macros", 4794 + "wasmtime-winch", 4795 + "wat", 4796 + "windows-sys 0.59.0", 4797 + ] 4798 + 4799 + [[package]] 4800 + name = "wasmtime-asm-macros" 4801 + version = "29.0.1" 4802 + source = "registry+https://github.com/rust-lang/crates.io-index" 4803 + checksum = "1f178b0d125201fbe9f75beaf849bd3e511891f9e45ba216a5b620802ccf64f2" 4804 + dependencies = [ 4805 + "cfg-if", 4806 + ] 4807 + 4808 + [[package]] 4809 + name = "wasmtime-cache" 4810 + version = "29.0.1" 4811 + source = "registry+https://github.com/rust-lang/crates.io-index" 4812 + checksum = "8b1161c8f62880deea07358bc40cceddc019f1c81d46007bc390710b2fe24ffc" 4813 + dependencies = [ 4814 + "anyhow", 4815 + "base64 0.21.7", 4816 + "directories-next", 4817 + "log", 4818 + "postcard", 4819 + "rustix 0.38.44", 4820 + "serde", 4821 + "serde_derive", 4822 + "sha2", 4823 + "toml", 4824 + "windows-sys 0.59.0", 4825 + "zstd", 4826 + ] 4827 + 4828 + [[package]] 4829 + name = "wasmtime-component-macro" 4830 + version = "29.0.1" 4831 + source = "registry+https://github.com/rust-lang/crates.io-index" 4832 + checksum = "d74de6592ed945d0a602f71243982a304d5d02f1e501b638addf57f42d57dfaf" 4833 + dependencies = [ 4834 + "anyhow", 4835 + "proc-macro2", 4836 + "quote", 4837 + "syn", 4838 + "wasmtime-component-util", 4839 + "wasmtime-wit-bindgen", 4840 + "wit-parser 0.221.3", 4841 + ] 4842 + 4843 + [[package]] 4844 + name = "wasmtime-component-util" 4845 + version = "29.0.1" 4846 + source = "registry+https://github.com/rust-lang/crates.io-index" 4847 + checksum = "707dc7b3c112ab5a366b30cfe2fb5b2f8e6a0f682f16df96a5ec582bfe6f056e" 4848 + 4849 + [[package]] 4850 + name = "wasmtime-cranelift" 4851 + version = "29.0.1" 4852 + source = "registry+https://github.com/rust-lang/crates.io-index" 4853 + checksum = "366be722674d4bf153290fbcbc4d7d16895cc82fb3e869f8d550ff768f9e9e87" 4854 + dependencies = [ 4855 + "anyhow", 4856 + "cfg-if", 4857 + "cranelift-codegen", 4858 + "cranelift-control", 4859 + "cranelift-entity", 4860 + "cranelift-frontend", 4861 + "cranelift-native", 4862 + "gimli", 4863 + "itertools", 4864 + "log", 4865 + "object 0.36.7", 4866 + "smallvec", 4867 + "target-lexicon", 4868 + "thiserror 1.0.69", 4869 + "wasmparser 0.221.3", 4870 + "wasmtime-environ", 4871 + "wasmtime-versioned-export-macros", 4872 + ] 4873 + 4874 + [[package]] 4875 + name = "wasmtime-environ" 4876 + version = "29.0.1" 4877 + source = "registry+https://github.com/rust-lang/crates.io-index" 4878 + checksum = "cdadc1af7097347aa276a4f008929810f726b5b46946971c660b6d421e9994ad" 4879 + dependencies = [ 4880 + "anyhow", 4881 + "cpp_demangle", 4882 + "cranelift-bitset", 4883 + "cranelift-entity", 4884 + "gimli", 4885 + "indexmap", 4886 + "log", 4887 + "object 0.36.7", 4888 + "postcard", 4889 + "rustc-demangle", 4890 + "semver", 4891 + "serde", 4892 + "serde_derive", 4893 + "smallvec", 4894 + "target-lexicon", 4895 + "wasm-encoder 0.221.3", 4896 + "wasmparser 0.221.3", 4897 + "wasmprinter", 4898 + "wasmtime-component-util", 4899 + ] 4900 + 4901 + [[package]] 4902 + name = "wasmtime-fiber" 4903 + version = "29.0.1" 4904 + source = "registry+https://github.com/rust-lang/crates.io-index" 4905 + checksum = "ccba90d4119f081bca91190485650730a617be1fff5228f8c4757ce133d21117" 4906 + dependencies = [ 4907 + "anyhow", 4908 + "cc", 4909 + "cfg-if", 4910 + "rustix 0.38.44", 4911 + "wasmtime-asm-macros", 4912 + "wasmtime-versioned-export-macros", 4913 + "windows-sys 0.59.0", 4914 + ] 4915 + 4916 + [[package]] 4917 + name = "wasmtime-jit-debug" 4918 + version = "29.0.1" 4919 + source = "registry+https://github.com/rust-lang/crates.io-index" 4920 + checksum = "3e7b61488a5ee00c35c8c22de707c36c0aecacf419a3be803a6a2ba5e860f56a" 4921 + dependencies = [ 4922 + "object 0.36.7", 4923 + "rustix 0.38.44", 4924 + "wasmtime-versioned-export-macros", 4925 + ] 4926 + 4927 + [[package]] 4928 + name = "wasmtime-jit-icache-coherence" 4929 + version = "29.0.1" 4930 + source = "registry+https://github.com/rust-lang/crates.io-index" 4931 + checksum = "ec5e8552e01692e6c2e5293171704fed8abdec79d1a6995a0870ab190e5747d1" 4932 + dependencies = [ 4933 + "anyhow", 4934 + "cfg-if", 4935 + "libc", 4936 + "windows-sys 0.59.0", 4937 + ] 4938 + 4939 + [[package]] 4940 + name = "wasmtime-math" 4941 + version = "29.0.1" 4942 + source = "registry+https://github.com/rust-lang/crates.io-index" 4943 + checksum = "29210ec2aa25e00f4d54605cedaf080f39ec01a872c5bd520ad04c67af1dde17" 4944 + dependencies = [ 4945 + "libm", 4946 + ] 4947 + 4948 + [[package]] 4949 + name = "wasmtime-slab" 4950 + version = "29.0.1" 4951 + source = "registry+https://github.com/rust-lang/crates.io-index" 4952 + checksum = "fcb5821a96fa04ac14bc7b158bb3d5cd7729a053db5a74dad396cd513a5e5ccf" 4953 + 4954 + [[package]] 4955 + name = "wasmtime-versioned-export-macros" 4956 + version = "29.0.1" 4957 + source = "registry+https://github.com/rust-lang/crates.io-index" 4958 + checksum = "86ff86db216dc0240462de40c8290887a613dddf9685508eb39479037ba97b5b" 4959 + dependencies = [ 4960 + "proc-macro2", 4961 + "quote", 4962 + "syn", 4963 + ] 4964 + 4965 + [[package]] 4966 + name = "wasmtime-wasi" 4967 + version = "29.0.1" 4968 + source = "registry+https://github.com/rust-lang/crates.io-index" 4969 + checksum = "8d1be69bfcab1bdac74daa7a1f9695ab992b9c8e21b9b061e7d66434097e0ca4" 4970 + dependencies = [ 4971 + "anyhow", 4972 + "async-trait", 4973 + "bitflags", 4974 + "bytes", 4975 + "cap-fs-ext", 4976 + "cap-net-ext", 4977 + "cap-rand", 4978 + "cap-std", 4979 + "cap-time-ext", 4980 + "fs-set-times", 4981 + "futures", 4982 + "io-extras", 4983 + "io-lifetimes", 4984 + "rustix 0.38.44", 4985 + "system-interface", 4986 + "thiserror 1.0.69", 4987 + "tokio", 4988 + "tracing", 4989 + "trait-variant", 4990 + "url", 4991 + "wasmtime", 4992 + "wiggle", 4993 + "windows-sys 0.59.0", 4994 + ] 4995 + 4996 + [[package]] 4997 + name = "wasmtime-winch" 4998 + version = "29.0.1" 4999 + source = "registry+https://github.com/rust-lang/crates.io-index" 5000 + checksum = "fdbabfb8f20502d5e1d81092b9ead3682ae59988487aafcd7567387b7a43cf8f" 5001 + dependencies = [ 5002 + "anyhow", 5003 + "cranelift-codegen", 5004 + "gimli", 5005 + "object 0.36.7", 5006 + "target-lexicon", 5007 + "wasmparser 0.221.3", 5008 + "wasmtime-cranelift", 5009 + "wasmtime-environ", 5010 + "winch-codegen", 5011 + ] 5012 + 5013 + [[package]] 5014 + name = "wasmtime-wit-bindgen" 5015 + version = "29.0.1" 5016 + source = "registry+https://github.com/rust-lang/crates.io-index" 5017 + checksum = "8358319c2dd1e4db79e3c1c5d3a5af84956615343f9f89f4e4996a36816e06e6" 5018 + dependencies = [ 5019 + "anyhow", 5020 + "heck", 5021 + "indexmap", 5022 + "wit-parser 0.221.3", 5023 + ] 5024 + 5025 + [[package]] 5026 + name = "wast" 5027 + version = "35.0.2" 5028 + source = "registry+https://github.com/rust-lang/crates.io-index" 5029 + checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" 5030 + dependencies = [ 5031 + "leb128", 5032 + ] 5033 + 5034 + [[package]] 5035 + name = "wast" 5036 + version = "245.0.1" 5037 + source = "registry+https://github.com/rust-lang/crates.io-index" 5038 + checksum = "28cf1149285569120b8ce39db8b465e8a2b55c34cbb586bd977e43e2bc7300bf" 5039 + dependencies = [ 5040 + "bumpalo", 5041 + "leb128fmt", 5042 + "memchr", 5043 + "unicode-width", 5044 + "wasm-encoder 0.245.1", 5045 + ] 5046 + 5047 + [[package]] 5048 + name = "wat" 5049 + version = "1.245.1" 5050 + source = "registry+https://github.com/rust-lang/crates.io-index" 5051 + checksum = "cd48d1679b6858988cb96b154dda0ec5bbb09275b71db46057be37332d5477be" 5052 + dependencies = [ 5053 + "wast 245.0.1", 5054 + ] 5055 + 5056 + [[package]] 3867 5057 name = "web-sys" 3868 5058 version = "0.3.85" 3869 5059 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3908 5098 checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" 3909 5099 dependencies = [ 3910 5100 "env_home", 3911 - "rustix", 5101 + "rustix 1.1.3", 3912 5102 "winsafe", 3913 5103 ] 3914 5104 ··· 3929 5119 checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" 3930 5120 3931 5121 [[package]] 5122 + name = "wiggle" 5123 + version = "29.0.1" 5124 + source = "registry+https://github.com/rust-lang/crates.io-index" 5125 + checksum = "4b9af35bc9629c52c261465320a9a07959164928b4241980ba1cf923b9e6751d" 5126 + dependencies = [ 5127 + "anyhow", 5128 + "async-trait", 5129 + "bitflags", 5130 + "thiserror 1.0.69", 5131 + "tracing", 5132 + "wasmtime", 5133 + "wiggle-macro", 5134 + ] 5135 + 5136 + [[package]] 5137 + name = "wiggle-generate" 5138 + version = "29.0.1" 5139 + source = "registry+https://github.com/rust-lang/crates.io-index" 5140 + checksum = "2cf267dd05673912c8138f4b54acabe6bd53407d9d1536f0fadb6520dd16e101" 5141 + dependencies = [ 5142 + "anyhow", 5143 + "heck", 5144 + "proc-macro2", 5145 + "quote", 5146 + "shellexpand", 5147 + "syn", 5148 + "witx", 5149 + ] 5150 + 5151 + [[package]] 5152 + name = "wiggle-macro" 5153 + version = "29.0.1" 5154 + source = "registry+https://github.com/rust-lang/crates.io-index" 5155 + checksum = "08c5c473d4198e6c2d377f3809f713ff0c110cab88a0805ae099a82119ee250c" 5156 + dependencies = [ 5157 + "proc-macro2", 5158 + "quote", 5159 + "syn", 5160 + "wiggle-generate", 5161 + ] 5162 + 5163 + [[package]] 5164 + name = "winapi" 5165 + version = "0.3.9" 5166 + source = "registry+https://github.com/rust-lang/crates.io-index" 5167 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 5168 + dependencies = [ 5169 + "winapi-i686-pc-windows-gnu", 5170 + "winapi-x86_64-pc-windows-gnu", 5171 + ] 5172 + 5173 + [[package]] 5174 + name = "winapi-i686-pc-windows-gnu" 5175 + version = "0.4.0" 5176 + source = "registry+https://github.com/rust-lang/crates.io-index" 5177 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 5178 + 5179 + [[package]] 5180 + name = "winapi-util" 5181 + version = "0.1.11" 5182 + source = "registry+https://github.com/rust-lang/crates.io-index" 5183 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 5184 + dependencies = [ 5185 + "windows-sys 0.61.2", 5186 + ] 5187 + 5188 + [[package]] 5189 + name = "winapi-x86_64-pc-windows-gnu" 5190 + version = "0.4.0" 5191 + source = "registry+https://github.com/rust-lang/crates.io-index" 5192 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 5193 + 5194 + [[package]] 5195 + name = "winch-codegen" 5196 + version = "29.0.1" 5197 + source = "registry+https://github.com/rust-lang/crates.io-index" 5198 + checksum = "2f849ef2c5f46cb0a20af4b4487aaa239846e52e2c03f13fa3c784684552859c" 5199 + dependencies = [ 5200 + "anyhow", 5201 + "cranelift-codegen", 5202 + "gimli", 5203 + "regalloc2", 5204 + "smallvec", 5205 + "target-lexicon", 5206 + "thiserror 1.0.69", 5207 + "wasmparser 0.221.3", 5208 + "wasmtime-cranelift", 5209 + "wasmtime-environ", 5210 + ] 5211 + 5212 + [[package]] 3932 5213 name = "windows-core" 3933 5214 version = "0.62.2" 3934 5215 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4012 5293 version = "0.52.0" 4013 5294 source = "registry+https://github.com/rust-lang/crates.io-index" 4014 5295 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 5296 + dependencies = [ 5297 + "windows-targets 0.52.6", 5298 + ] 5299 + 5300 + [[package]] 5301 + name = "windows-sys" 5302 + version = "0.59.0" 5303 + source = "registry+https://github.com/rust-lang/crates.io-index" 5304 + checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 4015 5305 dependencies = [ 4016 5306 "windows-targets 0.52.6", 4017 5307 ] ··· 4221 5511 checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 4222 5512 4223 5513 [[package]] 5514 + name = "winnow" 5515 + version = "0.7.15" 5516 + source = "registry+https://github.com/rust-lang/crates.io-index" 5517 + checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" 5518 + dependencies = [ 5519 + "memchr", 5520 + ] 5521 + 5522 + [[package]] 4224 5523 name = "winreg" 4225 5524 version = "0.50.0" 4226 5525 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4237 5536 checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" 4238 5537 4239 5538 [[package]] 5539 + name = "winx" 5540 + version = "0.36.4" 5541 + source = "registry+https://github.com/rust-lang/crates.io-index" 5542 + checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" 5543 + dependencies = [ 5544 + "bitflags", 5545 + "windows-sys 0.52.0", 5546 + ] 5547 + 5548 + [[package]] 4240 5549 name = "wiremock" 4241 5550 version = "0.6.5" 4242 5551 source = "registry+https://github.com/rust-lang/crates.io-index" 4243 5552 checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" 4244 5553 dependencies = [ 4245 5554 "assert-json-diff", 4246 - "base64", 5555 + "base64 0.22.1", 4247 5556 "deadpool", 4248 5557 "futures", 4249 5558 "http", ··· 4276 5585 dependencies = [ 4277 5586 "anyhow", 4278 5587 "heck", 4279 - "wit-parser", 5588 + "wit-parser 0.244.0", 4280 5589 ] 4281 5590 4282 5591 [[package]] ··· 4323 5632 "serde", 4324 5633 "serde_derive", 4325 5634 "serde_json", 4326 - "wasm-encoder", 5635 + "wasm-encoder 0.244.0", 4327 5636 "wasm-metadata", 4328 - "wasmparser", 4329 - "wit-parser", 5637 + "wasmparser 0.244.0", 5638 + "wit-parser 0.244.0", 5639 + ] 5640 + 5641 + [[package]] 5642 + name = "wit-parser" 5643 + version = "0.221.3" 5644 + source = "registry+https://github.com/rust-lang/crates.io-index" 5645 + checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac" 5646 + dependencies = [ 5647 + "anyhow", 5648 + "id-arena", 5649 + "indexmap", 5650 + "log", 5651 + "semver", 5652 + "serde", 5653 + "serde_derive", 5654 + "serde_json", 5655 + "unicode-xid", 5656 + "wasmparser 0.221.3", 4330 5657 ] 4331 5658 4332 5659 [[package]] ··· 4344 5671 "serde_derive", 4345 5672 "serde_json", 4346 5673 "unicode-xid", 4347 - "wasmparser", 5674 + "wasmparser 0.244.0", 5675 + ] 5676 + 5677 + [[package]] 5678 + name = "witx" 5679 + version = "0.9.1" 5680 + source = "registry+https://github.com/rust-lang/crates.io-index" 5681 + checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" 5682 + dependencies = [ 5683 + "anyhow", 5684 + "log", 5685 + "thiserror 1.0.69", 5686 + "wast 35.0.2", 4348 5687 ] 4349 5688 4350 5689 [[package]] ··· 4464 5803 version = "1.0.21" 4465 5804 source = "registry+https://github.com/rust-lang/crates.io-index" 4466 5805 checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" 5806 + 5807 + [[package]] 5808 + name = "zstd" 5809 + version = "0.13.3" 5810 + source = "registry+https://github.com/rust-lang/crates.io-index" 5811 + checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 5812 + dependencies = [ 5813 + "zstd-safe", 5814 + ] 5815 + 5816 + [[package]] 5817 + name = "zstd-safe" 5818 + version = "7.2.4" 5819 + source = "registry+https://github.com/rust-lang/crates.io-index" 5820 + checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" 5821 + dependencies = [ 5822 + "zstd-sys", 5823 + ] 5824 + 5825 + [[package]] 5826 + name = "zstd-sys" 5827 + version = "2.0.16+zstd.1.5.7" 5828 + source = "registry+https://github.com/rust-lang/crates.io-index" 5829 + checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" 5830 + dependencies = [ 5831 + "cc", 5832 + "pkg-config", 5833 + ]
+5
Cargo.toml
··· 5 5 default-run = "happyview" 6 6 7 7 [dependencies] 8 + aes-gcm = "0.10" 9 + anyhow = "1" 8 10 arc-swap = "1" 9 11 atrium-oauth = { version = "0.1", features = ["default-client"] } 10 12 atrium-identity = "0.1" ··· 37 39 tokio = { version = "1", features = ["full"] } 38 40 tokio-rustls = "0.26" 39 41 tokio-tungstenite = { version = "0.26", features = ["rustls-tls-webpki-roots"] } 42 + thiserror = "2" 40 43 tower = { version = "0.5", features = ["util"] } 41 44 tower-http = { version = "0.6", features = ["cors", "fs", "trace"] } 42 45 http-body-util = "0.1" ··· 47 50 tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } 48 51 urlencoding = "2.1.3" 49 52 webpki-roots = "0.26" 53 + wasmtime = { version = "29", features = ["async"] } 54 + wasmtime-wasi = "29" 50 55 regex = "1.12.3" 51 56 52 57 [[bin]]
+60
migrations/postgres/20260319000001_plugin_tables.sql
··· 1 + -- Plugin registry 2 + CREATE TABLE plugins ( 3 + id TEXT PRIMARY KEY, 4 + source TEXT NOT NULL CHECK (source IN ('file', 'url')), 5 + url TEXT, 6 + sha256 TEXT, 7 + enabled BOOLEAN NOT NULL DEFAULT true, 8 + loaded_at TIMESTAMPTZ, 9 + api_version TEXT NOT NULL 10 + ); 11 + 12 + -- Plugin configuration 13 + CREATE TABLE plugin_configs ( 14 + plugin_id TEXT PRIMARY KEY REFERENCES plugins(id) ON DELETE CASCADE, 15 + config JSONB NOT NULL DEFAULT '{}', 16 + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 17 + ); 18 + 19 + -- External account tokens (encrypted) 20 + CREATE TABLE external_account_tokens ( 21 + id TEXT PRIMARY KEY, 22 + did TEXT NOT NULL, 23 + plugin_id TEXT NOT NULL REFERENCES plugins(id) ON DELETE CASCADE, 24 + account_id TEXT NOT NULL, 25 + access_token BYTEA NOT NULL, 26 + refresh_token BYTEA, 27 + token_type TEXT, 28 + scope TEXT, 29 + expires_at TIMESTAMPTZ, 30 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 31 + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 32 + UNIQUE(did, plugin_id) 33 + ); 34 + 35 + -- Deduplication keys for sync records 36 + CREATE TABLE plugin_dedup_keys ( 37 + plugin_id TEXT NOT NULL REFERENCES plugins(id) ON DELETE CASCADE, 38 + did TEXT NOT NULL, 39 + dedup_key TEXT NOT NULL, 40 + record_uri TEXT NOT NULL, 41 + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 42 + PRIMARY KEY (plugin_id, did, dedup_key) 43 + ); 44 + 45 + -- KV storage for plugins (scoped per plugin + context) 46 + CREATE TABLE plugin_kv ( 47 + plugin_id TEXT NOT NULL REFERENCES plugins(id) ON DELETE CASCADE, 48 + scope TEXT NOT NULL, 49 + key TEXT NOT NULL, 50 + value BYTEA NOT NULL, 51 + expires_at TIMESTAMPTZ, 52 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 53 + PRIMARY KEY (plugin_id, scope, key) 54 + ); 55 + 56 + -- Index for KV expiration cleanup 57 + CREATE INDEX idx_plugin_kv_expires ON plugin_kv(expires_at) WHERE expires_at IS NOT NULL; 58 + 59 + -- Index for token lookup by DID 60 + CREATE INDEX idx_external_tokens_did ON external_account_tokens(did);
+60
migrations/sqlite/20260319000001_plugin_tables.sql
··· 1 + -- Plugin registry 2 + CREATE TABLE plugins ( 3 + id TEXT PRIMARY KEY, 4 + source TEXT NOT NULL CHECK (source IN ('file', 'url')), 5 + url TEXT, 6 + sha256 TEXT, 7 + enabled INTEGER NOT NULL DEFAULT 1, 8 + loaded_at TEXT, 9 + api_version TEXT NOT NULL 10 + ); 11 + 12 + -- Plugin configuration 13 + CREATE TABLE plugin_configs ( 14 + plugin_id TEXT PRIMARY KEY REFERENCES plugins(id) ON DELETE CASCADE, 15 + config TEXT NOT NULL DEFAULT '{}', 16 + updated_at TEXT NOT NULL DEFAULT (datetime('now')) 17 + ); 18 + 19 + -- External account tokens (encrypted) 20 + CREATE TABLE external_account_tokens ( 21 + id TEXT PRIMARY KEY, 22 + did TEXT NOT NULL, 23 + plugin_id TEXT NOT NULL REFERENCES plugins(id) ON DELETE CASCADE, 24 + account_id TEXT NOT NULL, 25 + access_token BLOB NOT NULL, 26 + refresh_token BLOB, 27 + token_type TEXT, 28 + scope TEXT, 29 + expires_at TEXT, 30 + created_at TEXT NOT NULL DEFAULT (datetime('now')), 31 + updated_at TEXT NOT NULL DEFAULT (datetime('now')), 32 + UNIQUE(did, plugin_id) 33 + ); 34 + 35 + -- Deduplication keys for sync records 36 + CREATE TABLE plugin_dedup_keys ( 37 + plugin_id TEXT NOT NULL REFERENCES plugins(id) ON DELETE CASCADE, 38 + did TEXT NOT NULL, 39 + dedup_key TEXT NOT NULL, 40 + record_uri TEXT NOT NULL, 41 + updated_at TEXT NOT NULL DEFAULT (datetime('now')), 42 + PRIMARY KEY (plugin_id, did, dedup_key) 43 + ); 44 + 45 + -- KV storage for plugins (scoped per plugin + context) 46 + CREATE TABLE plugin_kv ( 47 + plugin_id TEXT NOT NULL REFERENCES plugins(id) ON DELETE CASCADE, 48 + scope TEXT NOT NULL, 49 + key TEXT NOT NULL, 50 + value BLOB NOT NULL, 51 + expires_at TEXT, 52 + created_at TEXT NOT NULL DEFAULT (datetime('now')), 53 + PRIMARY KEY (plugin_id, scope, key) 54 + ); 55 + 56 + -- Index for KV expiration cleanup 57 + CREATE INDEX idx_plugin_kv_expires ON plugin_kv(expires_at) WHERE expires_at IS NOT NULL; 58 + 59 + -- Index for token lookup by DID 60 + CREATE INDEX idx_external_tokens_did ON external_account_tokens(did);
+9
src/config.rs
··· 21 21 pub logo_uri: Option<String>, 22 22 pub tos_uri: Option<String>, 23 23 pub policy_uri: Option<String>, 24 + pub token_encryption_key: Option<[u8; 32]>, 24 25 } 25 26 26 27 impl Config { ··· 55 56 logo_uri: env::var("LOGO_URI").ok(), 56 57 tos_uri: env::var("TOS_URI").ok(), 57 58 policy_uri: env::var("POLICY_URI").ok(), 59 + token_encryption_key: env::var("TOKEN_ENCRYPTION_KEY").ok().and_then(|s| { 60 + use base64::Engine; 61 + base64::engine::general_purpose::STANDARD 62 + .decode(&s) 63 + .ok() 64 + .and_then(|bytes| bytes.try_into().ok()) 65 + }), 58 66 } 59 67 } 60 68 ··· 120 128 logo_uri: None, 121 129 tos_uri: None, 122 130 policy_uri: None, 131 + token_encryption_key: None, 123 132 }; 124 133 assert_eq!( 125 134 config.listen_addr(),
+4
src/external_auth/mod.rs
··· 1 + mod routes; 2 + mod sync; 3 + 4 + pub use routes::routes;
+114
src/external_auth/routes.rs
··· 1 + use axum::{ 2 + Json, Router, 3 + extract::{Path, Query, State}, 4 + response::Redirect, 5 + routing::{get, post}, 6 + }; 7 + use serde::{Deserialize, Serialize}; 8 + 9 + use crate::AppState; 10 + use crate::error::AppError; 11 + 12 + pub fn routes() -> Router<AppState> { 13 + Router::new() 14 + .route("/providers", get(list_providers)) 15 + .route("/{plugin_id}/authorize", get(authorize)) 16 + .route("/{plugin_id}/callback", get(callback)) 17 + .route("/{plugin_id}/sync", post(sync)) 18 + .route("/{plugin_id}/unlink", post(unlink)) 19 + } 20 + 21 + #[derive(Serialize)] 22 + struct ProviderInfo { 23 + id: String, 24 + name: String, 25 + icon_url: Option<String>, 26 + } 27 + 28 + async fn list_providers( 29 + State(state): State<AppState>, 30 + ) -> Result<Json<Vec<ProviderInfo>>, AppError> { 31 + let plugins = state.plugin_registry.list().await; 32 + 33 + let providers: Vec<ProviderInfo> = plugins 34 + .into_iter() 35 + .map(|p| ProviderInfo { 36 + id: p.info.id.clone(), 37 + name: p.info.name.clone(), 38 + icon_url: p.info.icon_url.clone(), 39 + }) 40 + .collect(); 41 + 42 + Ok(Json(providers)) 43 + } 44 + 45 + #[derive(Deserialize)] 46 + struct AuthorizeQuery { 47 + redirect_uri: String, 48 + } 49 + 50 + async fn authorize( 51 + State(state): State<AppState>, 52 + Path(plugin_id): Path<String>, 53 + Query(query): Query<AuthorizeQuery>, 54 + ) -> Result<Json<serde_json::Value>, AppError> { 55 + let _plugin = state 56 + .plugin_registry 57 + .get(&plugin_id) 58 + .await 59 + .ok_or_else(|| AppError::NotFound(format!("Plugin not found: {}", plugin_id)))?; 60 + 61 + // Generate state parameter for CSRF protection 62 + let state_param = uuid::Uuid::new_v4().to_string(); 63 + 64 + // TODO: Store state in KV, call plugin's get_authorize_url() 65 + // For now, return placeholder 66 + let _ = query.redirect_uri; 67 + 68 + Ok(Json(serde_json::json!({ 69 + "authorize_url": format!("https://example.com/oauth?state={}", state_param), 70 + "state": state_param 71 + }))) 72 + } 73 + 74 + #[derive(Deserialize)] 75 + #[allow(dead_code)] // Fields used when full OAuth flow is implemented 76 + struct CallbackQuery { 77 + code: Option<String>, 78 + state: Option<String>, 79 + error: Option<String>, 80 + } 81 + 82 + async fn callback( 83 + State(_state): State<AppState>, 84 + Path(_plugin_id): Path<String>, 85 + Query(_query): Query<CallbackQuery>, 86 + ) -> Result<Redirect, AppError> { 87 + // TODO: Validate state, call plugin's handle_callback(), store tokens 88 + 89 + // For now, redirect to a placeholder 90 + Ok(Redirect::to("/")) 91 + } 92 + 93 + async fn sync( 94 + State(_state): State<AppState>, 95 + Path(_plugin_id): Path<String>, 96 + ) -> Result<Json<serde_json::Value>, AppError> { 97 + // TODO: Call plugin's sync_account(), process SyncRecords 98 + 99 + Ok(Json(serde_json::json!({ 100 + "status": "ok", 101 + "synced": 0 102 + }))) 103 + } 104 + 105 + async fn unlink( 106 + State(_state): State<AppState>, 107 + Path(_plugin_id): Path<String>, 108 + ) -> Result<Json<serde_json::Value>, AppError> { 109 + // TODO: Delete tokens, delete accountLink record 110 + 111 + Ok(Json(serde_json::json!({ 112 + "status": "ok" 113 + }))) 114 + }
+55
src/external_auth/sync.rs
··· 1 + use crate::db::adapt_sql; 2 + use crate::plugin::SyncRecord; 3 + 4 + #[allow(dead_code)] // Used when full sync flow is implemented 5 + #[derive(Debug, thiserror::Error)] 6 + pub enum SyncError { 7 + #[error("Database error: {0}")] 8 + Database(#[from] sqlx::Error), 9 + #[error("Validation error: {0}")] 10 + Validation(String), 11 + #[error("PDS write error: {0}")] 12 + PdsWrite(String), 13 + } 14 + 15 + /// Process sync records from a plugin 16 + #[allow(dead_code)] // Used when full sync flow is implemented 17 + pub async fn process_sync_records( 18 + db: &sqlx::AnyPool, 19 + db_backend: crate::db::DatabaseBackend, 20 + plugin_id: &str, 21 + user_did: &str, 22 + records: Vec<SyncRecord>, 23 + ) -> Result<usize, SyncError> { 24 + let mut processed = 0; 25 + 26 + for record in records { 27 + // TODO: Validate against lexicon schema 28 + // TODO: Check dedup_key 29 + // TODO: Sign attestation 30 + // TODO: Write to PDS 31 + 32 + // For now, just track dedup key 33 + if let Some(dedup_key) = &record.dedup_key { 34 + let sql = adapt_sql( 35 + "INSERT INTO plugin_dedup_keys (plugin_id, did, dedup_key, record_uri, updated_at) 36 + VALUES (?, ?, ?, ?, datetime('now')) 37 + ON CONFLICT (plugin_id, did, dedup_key) 38 + DO UPDATE SET record_uri = excluded.record_uri, updated_at = excluded.updated_at", 39 + db_backend, 40 + ); 41 + 42 + sqlx::query(&sql) 43 + .bind(plugin_id) 44 + .bind(user_did) 45 + .bind(dedup_key) 46 + .bind("at://placeholder") // TODO: Real URI after PDS write 47 + .execute(db) 48 + .await?; 49 + } 50 + 51 + processed += 1; 52 + } 53 + 54 + Ok(processed) 55 + }
+3
src/lib.rs
··· 5 5 pub mod dns; 6 6 pub mod error; 7 7 pub mod event_log; 8 + pub mod external_auth; 8 9 pub mod labeler; 9 10 pub mod lexicon; 10 11 pub mod lua; 12 + pub mod plugin; 11 13 pub mod profile; 12 14 pub mod rate_limit; 13 15 pub mod record_refs; ··· 56 58 pub rate_limiter: Arc<RateLimiter>, 57 59 pub oauth: Arc<HappyViewOAuthClient>, 58 60 pub cookie_key: axum_extra::extract::cookie::Key, 61 + pub plugin_registry: Arc<plugin::PluginRegistry>, 59 62 } 60 63 61 64 impl axum::extract::FromRef<AppState> for axum_extra::extract::cookie::Key {
+2
src/lua/atproto_api.rs
··· 222 222 logo_uri: None, 223 223 tos_uri: None, 224 224 policy_uri: None, 225 + token_encryption_key: None, 225 226 }; 226 227 let (tx, _) = watch::channel(vec![]); 227 228 let (labeler_tx, _) = watch::channel(()); ··· 287 288 cookie_key: axum_extra::extract::cookie::Key::derive_from( 288 289 b"test-secret-for-tests-only-not-production", 289 290 ), 291 + plugin_registry: std::sync::Arc::new(crate::plugin::PluginRegistry::new()), 290 292 } 291 293 } 292 294
+2
src/lua/db_api.rs
··· 630 630 logo_uri: None, 631 631 tos_uri: None, 632 632 policy_uri: None, 633 + token_encryption_key: None, 633 634 }; 634 635 let (tx, _) = watch::channel(vec![]); 635 636 let (labeler_tx, _) = watch::channel(()); ··· 695 696 cookie_key: axum_extra::extract::cookie::Key::derive_from( 696 697 b"test-secret-for-tests-only-not-production", 697 698 ), 699 + plugin_registry: std::sync::Arc::new(crate::plugin::PluginRegistry::new()), 698 700 } 699 701 } 700 702
+2
src/lua/execute.rs
··· 964 964 logo_uri: None, 965 965 tos_uri: None, 966 966 policy_uri: None, 967 + token_encryption_key: None, 967 968 }; 968 969 let (tx, _) = watch::channel(vec![]); 969 970 let (labeler_tx, _) = watch::channel(()); ··· 1029 1030 cookie_key: axum_extra::extract::cookie::Key::derive_from( 1030 1031 b"test-secret-for-tests-only-not-production", 1031 1032 ), 1033 + plugin_registry: std::sync::Arc::new(crate::plugin::PluginRegistry::new()), 1032 1034 } 1033 1035 } 1034 1036
+2
src/lua/http_api.rs
··· 104 104 logo_uri: None, 105 105 tos_uri: None, 106 106 policy_uri: None, 107 + token_encryption_key: None, 107 108 }; 108 109 let (tx, _) = watch::channel(vec![]); 109 110 let (labeler_tx, _) = watch::channel(()); ··· 169 170 cookie_key: axum_extra::extract::cookie::Key::derive_from( 170 171 b"test-secret-for-tests-only-not-production", 171 172 ), 173 + plugin_registry: std::sync::Arc::new(crate::plugin::PluginRegistry::new()), 172 174 } 173 175 } 174 176
+40
src/main.rs
··· 173 173 ); 174 174 } 175 175 176 + // Initialize plugin registry 177 + let plugin_registry = Arc::new(happyview::plugin::PluginRegistry::new()); 178 + 179 + // Load plugins from PLUGIN_URLS env var 180 + if let Ok(urls) = std::env::var("PLUGIN_URLS") { 181 + for (id, url, sha256) in happyview::plugin::loader::parse_plugin_urls(&urls) { 182 + match happyview::plugin::loader::load_from_url(&http, &url, sha256.as_deref()).await { 183 + Ok(plugin) => { 184 + tracing::info!(id = %id, "Loaded plugin from URL"); 185 + plugin_registry.register(plugin).await; 186 + } 187 + Err(e) => { 188 + tracing::error!(id = %id, error = %e, "Failed to load plugin"); 189 + } 190 + } 191 + } 192 + } 193 + 194 + // Load plugins from directory 195 + let plugin_dir = std::path::Path::new("./plugins"); 196 + if plugin_dir.exists() 197 + && let Ok(entries) = std::fs::read_dir(plugin_dir) 198 + { 199 + for entry in entries.flatten() { 200 + let path = entry.path(); 201 + if path.is_dir() { 202 + match happyview::plugin::loader::load_from_file(&path).await { 203 + Ok(plugin) => { 204 + tracing::info!(id = %plugin.info.id, "Loaded plugin from file"); 205 + plugin_registry.register(plugin).await; 206 + } 207 + Err(e) => { 208 + tracing::error!(path = %path.display(), error = %e, "Failed to load plugin"); 209 + } 210 + } 211 + } 212 + } 213 + } 214 + 176 215 // Initialize rate limiter from DB. 177 216 let rl_state = RateLimiter::load_from_db(&db_pool).await; 178 217 let rate_limiter = RateLimiter::new(rl_state.enabled, rl_state.global, rl_state.allowlist); ··· 279 318 rate_limiter, 280 319 oauth: Arc::new(oauth_client), 281 320 cookie_key, 321 + plugin_registry, 282 322 }; 283 323 284 324 // Sync initial collections to Tap on startup.
+111
src/plugin/encryption.rs
··· 1 + use aes_gcm::{ 2 + Aes256Gcm, Nonce, 3 + aead::{Aead, KeyInit}, 4 + }; 5 + use rand::RngCore; 6 + 7 + #[derive(Debug, thiserror::Error)] 8 + pub enum EncryptionError { 9 + #[error("Encryption key not configured")] 10 + KeyNotConfigured, 11 + #[error("Encryption failed")] 12 + EncryptionFailed, 13 + #[error("Decryption failed")] 14 + DecryptionFailed, 15 + #[error("Invalid ciphertext format")] 16 + InvalidFormat, 17 + } 18 + 19 + const NONCE_SIZE: usize = 12; 20 + 21 + /// Encrypt data using AES-256-GCM 22 + /// Returns: nonce || ciphertext || tag (concatenated) 23 + pub fn encrypt(key: &[u8; 32], plaintext: &[u8]) -> Result<Vec<u8>, EncryptionError> { 24 + let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| EncryptionError::EncryptionFailed)?; 25 + 26 + // Generate random nonce 27 + let mut nonce_bytes = [0u8; NONCE_SIZE]; 28 + rand::rng().fill_bytes(&mut nonce_bytes); 29 + let nonce = Nonce::from_slice(&nonce_bytes); 30 + 31 + // Encrypt 32 + let ciphertext = cipher 33 + .encrypt(nonce, plaintext) 34 + .map_err(|_| EncryptionError::EncryptionFailed)?; 35 + 36 + // Concatenate: nonce || ciphertext 37 + let mut result = Vec::with_capacity(NONCE_SIZE + ciphertext.len()); 38 + result.extend_from_slice(&nonce_bytes); 39 + result.extend_from_slice(&ciphertext); 40 + 41 + Ok(result) 42 + } 43 + 44 + /// Decrypt data encrypted with encrypt() 45 + pub fn decrypt(key: &[u8; 32], ciphertext: &[u8]) -> Result<Vec<u8>, EncryptionError> { 46 + if ciphertext.len() < NONCE_SIZE + 16 { 47 + // Minimum: nonce + auth tag 48 + return Err(EncryptionError::InvalidFormat); 49 + } 50 + 51 + let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| EncryptionError::DecryptionFailed)?; 52 + 53 + let nonce = Nonce::from_slice(&ciphertext[..NONCE_SIZE]); 54 + let encrypted = &ciphertext[NONCE_SIZE..]; 55 + 56 + cipher 57 + .decrypt(nonce, encrypted) 58 + .map_err(|_| EncryptionError::DecryptionFailed) 59 + } 60 + 61 + #[cfg(test)] 62 + mod tests { 63 + use super::*; 64 + 65 + #[test] 66 + fn test_encrypt_decrypt_roundtrip() { 67 + let key = [0x42u8; 32]; 68 + let plaintext = b"hello world"; 69 + 70 + let ciphertext = encrypt(&key, plaintext).unwrap(); 71 + assert_ne!(&ciphertext[NONCE_SIZE..], plaintext); 72 + 73 + let decrypted = decrypt(&key, &ciphertext).unwrap(); 74 + assert_eq!(decrypted, plaintext); 75 + } 76 + 77 + #[test] 78 + fn test_different_nonces() { 79 + let key = [0x42u8; 32]; 80 + let plaintext = b"hello world"; 81 + 82 + let ct1 = encrypt(&key, plaintext).unwrap(); 83 + let ct2 = encrypt(&key, plaintext).unwrap(); 84 + 85 + // Same plaintext should produce different ciphertext (different nonces) 86 + assert_ne!(ct1, ct2); 87 + 88 + // Both should decrypt correctly 89 + assert_eq!(decrypt(&key, &ct1).unwrap(), plaintext); 90 + assert_eq!(decrypt(&key, &ct2).unwrap(), plaintext); 91 + } 92 + 93 + #[test] 94 + fn test_invalid_ciphertext() { 95 + let key = [0x42u8; 32]; 96 + 97 + // Too short 98 + assert!(matches!( 99 + decrypt(&key, &[0u8; 10]), 100 + Err(EncryptionError::InvalidFormat) 101 + )); 102 + 103 + // Corrupted 104 + let mut ciphertext = encrypt(&key, b"hello").unwrap(); 105 + ciphertext[NONCE_SIZE] ^= 0xFF; 106 + assert!(matches!( 107 + decrypt(&key, &ciphertext), 108 + Err(EncryptionError::DecryptionFailed) 109 + )); 110 + } 111 + }
+133
src/plugin/host/http.rs
··· 1 + use super::{ 2 + HostContext, MAX_HTTP_REQUESTS, MAX_HTTP_RESPONSE_SIZE, MAX_HTTP_TOTAL_TRANSFER, ResourceUsage, 3 + }; 4 + use serde::{Deserialize, Serialize}; 5 + 6 + #[derive(Debug, Clone, Serialize, Deserialize)] 7 + pub struct HttpRequest { 8 + pub method: String, 9 + pub url: String, 10 + pub headers: Vec<(String, String)>, 11 + pub body: Option<Vec<u8>>, 12 + } 13 + 14 + #[derive(Debug, Clone, Serialize, Deserialize)] 15 + pub struct HttpResponse { 16 + pub status: u16, 17 + pub headers: Vec<(String, String)>, 18 + pub body: Vec<u8>, 19 + } 20 + 21 + #[derive(Debug, thiserror::Error)] 22 + pub enum HttpError { 23 + #[error("Too many requests: {0} > {MAX_HTTP_REQUESTS}")] 24 + TooManyRequests(u32), 25 + #[error("Response too large: {0} > {MAX_HTTP_RESPONSE_SIZE}")] 26 + ResponseTooLarge(u64), 27 + #[error("Transfer limit exceeded: {0} > {MAX_HTTP_TOTAL_TRANSFER}")] 28 + TransferLimitExceeded(u64), 29 + #[error("Request failed: {0}")] 30 + RequestFailed(#[from] reqwest::Error), 31 + } 32 + 33 + pub async fn http_request( 34 + ctx: &HostContext, 35 + usage: &mut ResourceUsage, 36 + req: HttpRequest, 37 + ) -> Result<HttpResponse, HttpError> { 38 + // Check request count limit 39 + usage.http_requests += 1; 40 + if usage.http_requests > MAX_HTTP_REQUESTS { 41 + return Err(HttpError::TooManyRequests(usage.http_requests)); 42 + } 43 + 44 + // Build request 45 + let method = req.method.parse().unwrap_or(reqwest::Method::GET); 46 + let mut builder = ctx.http_client.request(method, &req.url); 47 + 48 + for (name, value) in &req.headers { 49 + builder = builder.header(name, value); 50 + } 51 + 52 + if let Some(body) = req.body { 53 + usage.http_bytes_transferred += body.len() as u64; 54 + builder = builder.body(body); 55 + } 56 + 57 + // Check transfer limit before sending 58 + if usage.http_bytes_transferred > MAX_HTTP_TOTAL_TRANSFER { 59 + return Err(HttpError::TransferLimitExceeded( 60 + usage.http_bytes_transferred, 61 + )); 62 + } 63 + 64 + // Execute request 65 + let response = builder.send().await?; 66 + let status = response.status().as_u16(); 67 + 68 + let headers: Vec<(String, String)> = response 69 + .headers() 70 + .iter() 71 + .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())) 72 + .collect(); 73 + 74 + let body = response.bytes().await?; 75 + 76 + // Check response size 77 + if body.len() as u64 > MAX_HTTP_RESPONSE_SIZE { 78 + return Err(HttpError::ResponseTooLarge(body.len() as u64)); 79 + } 80 + 81 + usage.http_bytes_transferred += body.len() as u64; 82 + if usage.http_bytes_transferred > MAX_HTTP_TOTAL_TRANSFER { 83 + return Err(HttpError::TransferLimitExceeded( 84 + usage.http_bytes_transferred, 85 + )); 86 + } 87 + 88 + Ok(HttpResponse { 89 + status, 90 + headers, 91 + body: body.to_vec(), 92 + }) 93 + } 94 + 95 + #[cfg(test)] 96 + mod tests { 97 + use super::*; 98 + 99 + #[test] 100 + fn test_http_request_limit_check() { 101 + let mut usage = ResourceUsage { 102 + http_requests: MAX_HTTP_REQUESTS, 103 + ..Default::default() 104 + }; 105 + 106 + // Verify the limit check would fail 107 + usage.http_requests += 1; 108 + assert!(usage.http_requests > MAX_HTTP_REQUESTS); 109 + } 110 + 111 + #[test] 112 + fn test_http_transfer_limit_check() { 113 + let mut usage = ResourceUsage { 114 + http_bytes_transferred: MAX_HTTP_TOTAL_TRANSFER, 115 + ..Default::default() 116 + }; 117 + 118 + // Adding more would exceed limit 119 + usage.http_bytes_transferred += 1; 120 + assert!(usage.http_bytes_transferred > MAX_HTTP_TOTAL_TRANSFER); 121 + } 122 + 123 + #[test] 124 + fn test_http_response_struct() { 125 + let response = HttpResponse { 126 + status: 200, 127 + headers: vec![("content-type".into(), "application/json".into())], 128 + body: b"{}".to_vec(), 129 + }; 130 + assert_eq!(response.status, 200); 131 + assert_eq!(response.headers.len(), 1); 132 + } 133 + }
+104
src/plugin/host/kv.rs
··· 1 + use super::{HostContext, MAX_KV_SIZE_PER_USER, ResourceUsage}; 2 + use crate::db::adapt_sql; 3 + 4 + #[derive(Debug, thiserror::Error)] 5 + pub enum KvError { 6 + #[error("Storage quota exceeded: {0} > {MAX_KV_SIZE_PER_USER}")] 7 + QuotaExceeded(u64), 8 + #[error("Database error: {0}")] 9 + Database(#[from] sqlx::Error), 10 + } 11 + 12 + pub async fn kv_get(ctx: &HostContext, key: &str) -> Result<Option<Vec<u8>>, KvError> { 13 + let sql = adapt_sql( 14 + "SELECT value FROM plugin_kv 15 + WHERE plugin_id = ? AND scope = ? AND key = ? 16 + AND (expires_at IS NULL OR expires_at > datetime('now'))", 17 + ctx.db_backend, 18 + ); 19 + 20 + let result: Option<(Vec<u8>,)> = sqlx::query_as(&sql) 21 + .bind(&ctx.plugin_id) 22 + .bind(&ctx.scope) 23 + .bind(key) 24 + .fetch_optional(&ctx.db) 25 + .await?; 26 + 27 + Ok(result.map(|(v,)| v)) 28 + } 29 + 30 + pub async fn kv_set( 31 + ctx: &HostContext, 32 + usage: &mut ResourceUsage, 33 + key: &str, 34 + value: Vec<u8>, 35 + ttl_secs: Option<u32>, 36 + ) -> Result<(), KvError> { 37 + // Check quota (simple check - full implementation would sum all keys) 38 + usage.kv_bytes_used += value.len() as u64; 39 + if usage.kv_bytes_used > MAX_KV_SIZE_PER_USER { 40 + return Err(KvError::QuotaExceeded(usage.kv_bytes_used)); 41 + } 42 + 43 + let expires_at = ttl_secs 44 + .map(|secs| (chrono::Utc::now() + chrono::Duration::seconds(secs as i64)).to_rfc3339()); 45 + 46 + // Upsert 47 + let sql = adapt_sql( 48 + "INSERT INTO plugin_kv (plugin_id, scope, key, value, expires_at, created_at) 49 + VALUES (?, ?, ?, ?, ?, datetime('now')) 50 + ON CONFLICT (plugin_id, scope, key) 51 + DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at", 52 + ctx.db_backend, 53 + ); 54 + 55 + sqlx::query(&sql) 56 + .bind(&ctx.plugin_id) 57 + .bind(&ctx.scope) 58 + .bind(key) 59 + .bind(&value) 60 + .bind(expires_at) 61 + .execute(&ctx.db) 62 + .await?; 63 + 64 + Ok(()) 65 + } 66 + 67 + pub async fn kv_delete(ctx: &HostContext, key: &str) -> Result<(), KvError> { 68 + let sql = adapt_sql( 69 + "DELETE FROM plugin_kv WHERE plugin_id = ? AND scope = ? AND key = ?", 70 + ctx.db_backend, 71 + ); 72 + 73 + sqlx::query(&sql) 74 + .bind(&ctx.plugin_id) 75 + .bind(&ctx.scope) 76 + .bind(key) 77 + .execute(&ctx.db) 78 + .await?; 79 + 80 + Ok(()) 81 + } 82 + 83 + #[cfg(test)] 84 + mod tests { 85 + use super::*; 86 + 87 + #[test] 88 + fn test_quota_exceeded_check() { 89 + let mut usage = ResourceUsage { 90 + kv_bytes_used: MAX_KV_SIZE_PER_USER, 91 + ..Default::default() 92 + }; 93 + 94 + // Adding more would exceed quota 95 + usage.kv_bytes_used += 1; 96 + assert!(usage.kv_bytes_used > MAX_KV_SIZE_PER_USER); 97 + } 98 + 99 + #[test] 100 + fn test_kv_error_display() { 101 + let err = KvError::QuotaExceeded(2_000_000); 102 + assert!(err.to_string().contains("exceeded")); 103 + } 104 + }
+70
src/plugin/host/logging.rs
··· 1 + use std::str::FromStr; 2 + use tracing::{debug, error, info, warn}; 3 + 4 + /// Log level for plugin logging 5 + #[derive(Debug, Clone, Copy, Default)] 6 + pub enum LogLevel { 7 + Debug, 8 + #[default] 9 + Info, 10 + Warn, 11 + Error, 12 + } 13 + 14 + impl FromStr for LogLevel { 15 + type Err = std::convert::Infallible; 16 + 17 + fn from_str(s: &str) -> Result<Self, Self::Err> { 18 + Ok(match s.to_lowercase().as_str() { 19 + "debug" => Self::Debug, 20 + "info" => Self::Info, 21 + "warn" | "warning" => Self::Warn, 22 + "error" => Self::Error, 23 + _ => Self::Info, 24 + }) 25 + } 26 + } 27 + 28 + /// Log a message from a plugin 29 + pub fn log(plugin_id: &str, level: LogLevel, message: &str) { 30 + match level { 31 + LogLevel::Debug => debug!(plugin = %plugin_id, "{}", message), 32 + LogLevel::Info => info!(plugin = %plugin_id, "{}", message), 33 + LogLevel::Warn => warn!(plugin = %plugin_id, "{}", message), 34 + LogLevel::Error => error!(plugin = %plugin_id, "{}", message), 35 + } 36 + } 37 + 38 + #[cfg(test)] 39 + mod tests { 40 + use super::*; 41 + 42 + #[test] 43 + fn test_log_level_from_str_known_values() { 44 + assert!(matches!("debug".parse::<LogLevel>(), Ok(LogLevel::Debug))); 45 + assert!(matches!("DEBUG".parse::<LogLevel>(), Ok(LogLevel::Debug))); 46 + assert!(matches!("info".parse::<LogLevel>(), Ok(LogLevel::Info))); 47 + assert!(matches!("INFO".parse::<LogLevel>(), Ok(LogLevel::Info))); 48 + assert!(matches!("warn".parse::<LogLevel>(), Ok(LogLevel::Warn))); 49 + assert!(matches!("warning".parse::<LogLevel>(), Ok(LogLevel::Warn))); 50 + assert!(matches!("WARN".parse::<LogLevel>(), Ok(LogLevel::Warn))); 51 + assert!(matches!("error".parse::<LogLevel>(), Ok(LogLevel::Error))); 52 + assert!(matches!("ERROR".parse::<LogLevel>(), Ok(LogLevel::Error))); 53 + } 54 + 55 + #[test] 56 + fn test_log_level_from_str_unknown_defaults_to_info() { 57 + assert!(matches!("trace".parse::<LogLevel>(), Ok(LogLevel::Info))); 58 + assert!(matches!("".parse::<LogLevel>(), Ok(LogLevel::Info))); 59 + assert!(matches!("unknown".parse::<LogLevel>(), Ok(LogLevel::Info))); 60 + } 61 + 62 + #[test] 63 + fn test_log_does_not_panic() { 64 + // Verify log() runs without panicking for each level 65 + log("test-plugin", LogLevel::Debug, "debug message"); 66 + log("test-plugin", LogLevel::Info, "info message"); 67 + log("test-plugin", LogLevel::Warn, "warn message"); 68 + log("test-plugin", LogLevel::Error, "error message"); 69 + } 70 + }
+67
src/plugin/host/lookup.rs
··· 1 + use super::HostContext; 2 + use crate::db::adapt_sql; 3 + use crate::plugin::StrongRef; 4 + 5 + #[derive(Debug, thiserror::Error)] 6 + pub enum LookupError { 7 + #[error("Database error: {0}")] 8 + Database(#[from] sqlx::Error), 9 + #[error("Invalid external ID field path")] 10 + InvalidFieldPath, 11 + } 12 + 13 + /// Look up a record by external ID 14 + /// 15 + /// # Arguments 16 + /// * `collection` - Lexicon collection ID (e.g., "games.gamesgamesgamesgames.game") 17 + /// * `external_id_field` - JSON path to external ID field (e.g., "externalIds.steam") 18 + /// * `external_id_value` - Value to match 19 + pub async fn lookup_record( 20 + ctx: &HostContext, 21 + collection: &str, 22 + external_id_field: &str, 23 + external_id_value: &str, 24 + ) -> Result<Option<StrongRef>, LookupError> { 25 + // Validate field path (basic check) 26 + if external_id_field.is_empty() || external_id_field.contains("..") { 27 + return Err(LookupError::InvalidFieldPath); 28 + } 29 + 30 + // Build JSON path for query 31 + let json_path = format!("$.{}", external_id_field); 32 + 33 + let sql = adapt_sql( 34 + "SELECT uri, cid FROM records 35 + WHERE collection = ? 36 + AND json_extract(record, ?) = ? 37 + LIMIT 1", 38 + ctx.db_backend, 39 + ); 40 + 41 + let result: Option<(String, String)> = sqlx::query_as(&sql) 42 + .bind(collection) 43 + .bind(&json_path) 44 + .bind(external_id_value) 45 + .fetch_optional(&ctx.db) 46 + .await?; 47 + 48 + Ok(result.map(|(uri, cid)| StrongRef { uri, cid })) 49 + } 50 + 51 + #[cfg(test)] 52 + mod tests { 53 + use super::*; 54 + 55 + #[test] 56 + fn test_invalid_field_path_empty() { 57 + // Can't test async without runtime, but we can verify error types exist 58 + let err = LookupError::InvalidFieldPath; 59 + assert!(err.to_string().contains("Invalid")); 60 + } 61 + 62 + #[test] 63 + fn test_lookup_error_display() { 64 + let err = LookupError::InvalidFieldPath; 65 + assert_eq!(err.to_string(), "Invalid external ID field path"); 66 + } 67 + }
+41
src/plugin/host/mod.rs
··· 1 + mod http; 2 + mod kv; 3 + mod logging; 4 + mod lookup; 5 + mod secrets; 6 + 7 + pub use http::*; 8 + pub use kv::*; 9 + pub use logging::*; 10 + pub use lookup::*; 11 + pub use secrets::*; 12 + 13 + use std::collections::HashMap; 14 + use std::sync::Arc; 15 + 16 + /// Context passed to all host function calls 17 + pub struct HostContext { 18 + pub plugin_id: String, 19 + pub scope: String, // user DID or OAuth state 20 + pub secrets: HashMap<String, String>, 21 + pub config: serde_json::Value, 22 + pub db: sqlx::AnyPool, 23 + pub db_backend: crate::db::DatabaseBackend, 24 + pub http_client: reqwest::Client, 25 + pub lexicons: Arc<crate::lexicon::LexiconRegistry>, 26 + } 27 + 28 + /// Resource usage tracking for limits 29 + #[derive(Default)] 30 + pub struct ResourceUsage { 31 + pub http_requests: u32, 32 + pub http_bytes_transferred: u64, 33 + pub kv_bytes_used: u64, 34 + } 35 + 36 + /// Resource limits from spec 37 + pub const MAX_HTTP_REQUESTS: u32 = 100; 38 + pub const MAX_HTTP_RESPONSE_SIZE: u64 = 100 * 1024 * 1024; // 100 MB 39 + pub const MAX_HTTP_TOTAL_TRANSFER: u64 = 500 * 1024 * 1024; // 500 MB 40 + pub const MAX_HTTP_CONCURRENT: usize = 5; 41 + pub const MAX_KV_SIZE_PER_USER: u64 = 1024 * 1024; // 1 MB
+33
src/plugin/host/secrets.rs
··· 1 + use super::HostContext; 2 + 3 + /// Get a secret value by name (from pre-loaded secrets map) 4 + pub fn get_secret(ctx: &HostContext, name: &str) -> Option<String> { 5 + ctx.secrets.get(name).cloned() 6 + } 7 + 8 + #[cfg(test)] 9 + mod tests { 10 + use std::collections::HashMap; 11 + 12 + #[test] 13 + fn test_get_existing_secret() { 14 + let mut secrets: HashMap<String, String> = HashMap::new(); 15 + secrets.insert("API_KEY".to_string(), "abc123".to_string()); 16 + assert_eq!(secrets.get("API_KEY").cloned(), Some("abc123".to_string())); 17 + } 18 + 19 + #[test] 20 + fn test_get_missing_secret() { 21 + let secrets: HashMap<String, String> = HashMap::new(); 22 + assert_eq!(secrets.get("MISSING").cloned(), None); 23 + } 24 + 25 + #[test] 26 + fn test_get_secret_case_sensitive() { 27 + let mut secrets: HashMap<String, String> = HashMap::new(); 28 + secrets.insert("api_key".to_string(), "lower".to_string()); 29 + // Keys are case-sensitive 30 + assert_eq!(secrets.get("API_KEY").cloned(), None); 31 + assert_eq!(secrets.get("api_key").cloned(), Some("lower".to_string())); 32 + } 33 + }
+201
src/plugin/loader.rs
··· 1 + use crate::plugin::{LoadedPlugin, PluginInfo, PluginSource}; 2 + use sha2::{Digest, Sha256}; 3 + use std::path::Path; 4 + 5 + const SUPPORTED_API_VERSION: &str = "1"; 6 + 7 + #[derive(Debug, thiserror::Error)] 8 + pub enum LoadError { 9 + #[error("Failed to read plugin file: {0}")] 10 + ReadFile(#[from] std::io::Error), 11 + #[error("Failed to download plugin: {0}")] 12 + Download(#[from] reqwest::Error), 13 + #[error("SHA256 mismatch: expected {expected}, got {actual}")] 14 + Sha256Mismatch { expected: String, actual: String }, 15 + #[error("Failed to parse plugin info: {0}")] 16 + ParseInfo(#[from] serde_json::Error), 17 + #[error("Plugin API version {0} not supported (requires {SUPPORTED_API_VERSION})")] 18 + UnsupportedApiVersion(String), 19 + #[error("Missing required secret: {0}")] 20 + MissingSecret(String), 21 + #[error("WASM validation failed: {0}")] 22 + WasmValidation(String), 23 + } 24 + 25 + /// Load a plugin from a file path 26 + pub async fn load_from_file(path: &Path) -> Result<LoadedPlugin, LoadError> { 27 + let wasm_path = path.join("plugin.wasm"); 28 + let wasm_bytes = tokio::fs::read(&wasm_path).await?; 29 + 30 + // Try to load plugin.toml for metadata override 31 + let toml_path = path.join("plugin.toml"); 32 + let _toml_content = tokio::fs::read_to_string(&toml_path).await.ok(); 33 + 34 + // Extract plugin info by instantiating WASM and calling plugin_info() 35 + // For now, create placeholder - full implementation needs wasmtime integration 36 + let info = extract_plugin_info(&wasm_bytes)?; 37 + 38 + validate_api_version(&info)?; 39 + 40 + Ok(LoadedPlugin { 41 + info, 42 + source: PluginSource::File { 43 + path: path.to_path_buf(), 44 + }, 45 + wasm_bytes, 46 + }) 47 + } 48 + 49 + /// Load a plugin from a URL 50 + pub async fn load_from_url( 51 + client: &reqwest::Client, 52 + url: &str, 53 + expected_sha256: Option<&str>, 54 + ) -> Result<LoadedPlugin, LoadError> { 55 + let response = client.get(url).send().await?.error_for_status()?; 56 + let wasm_bytes = response.bytes().await?.to_vec(); 57 + 58 + // Verify SHA256 if provided 59 + if let Some(expected) = expected_sha256 { 60 + let mut hasher = Sha256::new(); 61 + hasher.update(&wasm_bytes); 62 + let actual = hex::encode(hasher.finalize()); 63 + 64 + if actual != expected { 65 + return Err(LoadError::Sha256Mismatch { 66 + expected: expected.to_string(), 67 + actual, 68 + }); 69 + } 70 + } 71 + 72 + let info = extract_plugin_info(&wasm_bytes)?; 73 + validate_api_version(&info)?; 74 + 75 + Ok(LoadedPlugin { 76 + info, 77 + source: PluginSource::Url { 78 + url: url.to_string(), 79 + sha256: expected_sha256.map(String::from), 80 + }, 81 + wasm_bytes, 82 + }) 83 + } 84 + 85 + /// Extract plugin info by instantiating WASM and calling plugin_info() 86 + fn extract_plugin_info(wasm_bytes: &[u8]) -> Result<PluginInfo, LoadError> { 87 + // TODO: Full implementation with wasmtime 88 + // For now, this is a placeholder that will be filled in when we integrate wasmtime calls 89 + 90 + // Validate it's valid WASM 91 + wasmtime::Module::validate(&wasmtime::Engine::default(), wasm_bytes) 92 + .map_err(|e| LoadError::WasmValidation(e.to_string()))?; 93 + 94 + // Return placeholder - real implementation calls plugin_info() export 95 + Ok(PluginInfo { 96 + id: "placeholder".into(), 97 + name: "Placeholder".into(), 98 + version: "0.0.0".into(), 99 + api_version: SUPPORTED_API_VERSION.into(), 100 + icon_url: None, 101 + required_secrets: vec![], 102 + config_schema: None, 103 + }) 104 + } 105 + 106 + fn validate_api_version(info: &PluginInfo) -> Result<(), LoadError> { 107 + // Parse as integer for comparison 108 + let plugin_version: u32 = info.api_version.parse().unwrap_or(0); 109 + let supported_version: u32 = SUPPORTED_API_VERSION.parse().unwrap_or(1); 110 + 111 + if plugin_version > supported_version { 112 + return Err(LoadError::UnsupportedApiVersion(info.api_version.clone())); 113 + } 114 + 115 + Ok(()) 116 + } 117 + 118 + /// Validate that all required secrets are present 119 + pub fn validate_secrets( 120 + info: &PluginInfo, 121 + available_secrets: &std::collections::HashMap<String, String>, 122 + ) -> Result<(), LoadError> { 123 + for secret in &info.required_secrets { 124 + if !available_secrets.contains_key(secret) { 125 + return Err(LoadError::MissingSecret(secret.clone())); 126 + } 127 + } 128 + Ok(()) 129 + } 130 + 131 + /// Parse PLUGIN_URLS environment variable 132 + /// Format: id|url|sha256:hash,id|url|sha256:hash,... 133 + pub fn parse_plugin_urls(env_value: &str) -> Vec<(String, String, Option<String>)> { 134 + env_value 135 + .split(',') 136 + .filter_map(|entry| { 137 + let parts: Vec<&str> = entry.trim().split('|').collect(); 138 + if parts.len() >= 2 { 139 + let id = parts[0].to_string(); 140 + let url = parts[1].to_string(); 141 + let sha256 = parts 142 + .get(2) 143 + .and_then(|s| s.strip_prefix("sha256:").map(String::from)); 144 + Some((id, url, sha256)) 145 + } else { 146 + None 147 + } 148 + }) 149 + .collect() 150 + } 151 + 152 + #[cfg(test)] 153 + mod tests { 154 + use super::*; 155 + 156 + #[test] 157 + fn test_parse_plugin_urls() { 158 + let input = 159 + "steam|https://example.com/steam.wasm|sha256:abc123,gog|https://example.com/gog.wasm"; 160 + let result = parse_plugin_urls(input); 161 + 162 + assert_eq!(result.len(), 2); 163 + assert_eq!( 164 + result[0], 165 + ( 166 + "steam".into(), 167 + "https://example.com/steam.wasm".into(), 168 + Some("abc123".into()) 169 + ) 170 + ); 171 + assert_eq!( 172 + result[1], 173 + ("gog".into(), "https://example.com/gog.wasm".into(), None) 174 + ); 175 + } 176 + 177 + #[test] 178 + fn test_validate_api_version() { 179 + let info = PluginInfo { 180 + id: "test".into(), 181 + name: "Test".into(), 182 + version: "1.0.0".into(), 183 + api_version: "1".into(), 184 + icon_url: None, 185 + required_secrets: vec![], 186 + config_schema: None, 187 + }; 188 + 189 + assert!(validate_api_version(&info).is_ok()); 190 + 191 + let future_info = PluginInfo { 192 + api_version: "99".into(), 193 + ..info 194 + }; 195 + 196 + assert!(matches!( 197 + validate_api_version(&future_info), 198 + Err(LoadError::UnsupportedApiVersion(_)) 199 + )); 200 + } 201 + }
+41
src/plugin/mod.rs
··· 1 + pub mod encryption; 2 + pub mod host; 3 + pub mod loader; 4 + mod runtime; 5 + mod types; 6 + 7 + pub use runtime::WasmRuntime; 8 + pub use types::*; 9 + 10 + use std::collections::HashMap; 11 + use std::sync::Arc; 12 + use tokio::sync::RwLock; 13 + 14 + /// Registry of loaded plugins 15 + #[derive(Default)] 16 + pub struct PluginRegistry { 17 + plugins: RwLock<HashMap<String, Arc<LoadedPlugin>>>, 18 + } 19 + 20 + impl PluginRegistry { 21 + pub fn new() -> Self { 22 + Self::default() 23 + } 24 + 25 + pub async fn register(&self, plugin: LoadedPlugin) { 26 + let id = plugin.info.id.clone(); 27 + self.plugins.write().await.insert(id, Arc::new(plugin)); 28 + } 29 + 30 + pub async fn get(&self, id: &str) -> Option<Arc<LoadedPlugin>> { 31 + self.plugins.read().await.get(id).cloned() 32 + } 33 + 34 + pub async fn list(&self) -> Vec<Arc<LoadedPlugin>> { 35 + self.plugins.read().await.values().cloned().collect() 36 + } 37 + 38 + pub async fn remove(&self, id: &str) -> Option<Arc<LoadedPlugin>> { 39 + self.plugins.write().await.remove(id) 40 + } 41 + }
+27
src/plugin/runtime.rs
··· 1 + use wasmtime::*; 2 + 3 + /// WASM runtime for executing plugins 4 + pub struct WasmRuntime { 5 + engine: Engine, 6 + } 7 + 8 + impl WasmRuntime { 9 + pub fn new() -> Result<Self, anyhow::Error> { 10 + let mut config = Config::new(); 11 + config.async_support(true); 12 + 13 + let engine = Engine::new(&config)?; 14 + 15 + Ok(Self { engine }) 16 + } 17 + 18 + pub fn engine(&self) -> &Engine { 19 + &self.engine 20 + } 21 + } 22 + 23 + impl Default for WasmRuntime { 24 + fn default() -> Self { 25 + Self::new().expect("Failed to create WASM runtime") 26 + } 27 + }
+98
src/plugin/types.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + /// Plugin metadata returned by plugin_info() 4 + #[derive(Debug, Clone, Serialize, Deserialize)] 5 + pub struct PluginInfo { 6 + pub id: String, 7 + pub name: String, 8 + pub version: String, 9 + pub api_version: String, 10 + #[serde(skip_serializing_if = "Option::is_none")] 11 + pub icon_url: Option<String>, 12 + #[serde(default)] 13 + pub required_secrets: Vec<String>, 14 + #[serde(skip_serializing_if = "Option::is_none")] 15 + pub config_schema: Option<serde_json::Value>, 16 + } 17 + 18 + /// OAuth callback parameters passed to handle_callback() 19 + #[derive(Debug, Clone, Serialize, Deserialize)] 20 + pub struct CallbackParams { 21 + pub code: Option<String>, 22 + pub state: Option<String>, 23 + pub error: Option<String>, 24 + #[serde(flatten)] 25 + pub extra: std::collections::HashMap<String, String>, 26 + } 27 + 28 + /// Tokens returned by handle_callback() and refresh_tokens() 29 + #[derive(Debug, Clone, Serialize, Deserialize)] 30 + pub struct TokenSet { 31 + pub access_token: String, 32 + #[serde(skip_serializing_if = "Option::is_none")] 33 + pub refresh_token: Option<String>, 34 + #[serde(skip_serializing_if = "Option::is_none")] 35 + pub expires_at: Option<chrono::DateTime<chrono::Utc>>, 36 + pub token_type: String, 37 + } 38 + 39 + /// Error returned by plugin functions 40 + #[derive(Debug, Clone, Serialize, Deserialize)] 41 + pub struct PluginError { 42 + pub code: PluginErrorCode, 43 + pub message: String, 44 + #[serde(default)] 45 + pub retryable: bool, 46 + } 47 + 48 + #[derive(Debug, Clone, Serialize, Deserialize)] 49 + #[serde(rename_all = "snake_case")] 50 + pub enum PluginErrorCode { 51 + UserDenied, 52 + InvalidToken, 53 + ServiceUnavailable, 54 + InvalidResponse, 55 + Unknown, 56 + } 57 + 58 + /// External profile returned by get_profile() 59 + #[derive(Debug, Clone, Serialize, Deserialize)] 60 + pub struct ExternalProfile { 61 + pub account_id: String, 62 + #[serde(skip_serializing_if = "Option::is_none")] 63 + pub display_name: Option<String>, 64 + #[serde(skip_serializing_if = "Option::is_none")] 65 + pub profile_url: Option<String>, 66 + #[serde(skip_serializing_if = "Option::is_none")] 67 + pub avatar_url: Option<String>, 68 + } 69 + 70 + /// Record returned by sync_account() - lexicon-aware 71 + #[derive(Debug, Clone, Serialize, Deserialize)] 72 + pub struct SyncRecord { 73 + pub collection: String, 74 + pub record: serde_json::Value, 75 + #[serde(skip_serializing_if = "Option::is_none")] 76 + pub dedup_key: Option<String>, 77 + } 78 + 79 + /// Strong reference to an AT Protocol record 80 + #[derive(Debug, Clone, Serialize, Deserialize)] 81 + pub struct StrongRef { 82 + pub uri: String, 83 + pub cid: String, 84 + } 85 + 86 + /// Plugin source - file or URL 87 + #[derive(Debug, Clone)] 88 + pub enum PluginSource { 89 + File { path: std::path::PathBuf }, 90 + Url { url: String, sha256: Option<String> }, 91 + } 92 + 93 + /// Loaded plugin with runtime state 94 + pub struct LoadedPlugin { 95 + pub info: PluginInfo, 96 + pub source: PluginSource, 97 + pub wasm_bytes: Vec<u8>, 98 + }
+1
src/server.rs
··· 67 67 .route("/settings/logo", get(crate::admin::settings::serve_logo)) 68 68 .nest("/admin", admin::admin_routes(state.clone())) 69 69 .nest("/auth", crate::auth::routes::routes()) 70 + .nest("/external-auth", crate::external_auth::routes()) 70 71 .route("/oauth/client-metadata.json", get(client_metadata)) 71 72 .route("/xrpc/app.bsky.actor.getProfile", get(get_profile)) 72 73 .route(
+2
tests/common/app.rs
··· 51 51 logo_uri: None, 52 52 tos_uri: None, 53 53 policy_uri: None, 54 + token_encryption_key: None, 54 55 }; 55 56 56 57 let sql = adapt_sql( ··· 129 130 cookie_key: axum_extra::extract::cookie::Key::derive_from( 130 131 b"test-secret-that-is-at-least-32-bytes-long", 131 132 ), 133 + plugin_registry: std::sync::Arc::new(happyview::plugin::PluginRegistry::new()), 132 134 }; 133 135 134 136 let router = server::router(state.clone());
+2
tests/lua_atproto_api.rs
··· 33 33 logo_uri: None, 34 34 tos_uri: None, 35 35 policy_uri: None, 36 + token_encryption_key: None, 36 37 }; 37 38 let (tx, _) = watch::channel(vec![]); 38 39 let (labeler_tx, _) = watch::channel(()); ··· 83 84 ), 84 85 oauth: std::sync::Arc::new(oauth), 85 86 cookie_key: axum_extra::extract::cookie::Key::derive_from(b"test-secret"), 87 + plugin_registry: std::sync::Arc::new(happyview::plugin::PluginRegistry::new()), 86 88 } 87 89 } 88 90
+2
tests/lua_db_api.rs
··· 36 36 logo_uri: None, 37 37 tos_uri: None, 38 38 policy_uri: None, 39 + token_encryption_key: None, 39 40 }; 40 41 let (tx, _) = watch::channel(vec![]); 41 42 let (labeler_tx, _) = watch::channel(()); ··· 86 87 ), 87 88 oauth: std::sync::Arc::new(oauth), 88 89 cookie_key: axum_extra::extract::cookie::Key::derive_from(b"test-secret"), 90 + plugin_registry: std::sync::Arc::new(happyview::plugin::PluginRegistry::new()), 89 91 } 90 92 } 91 93
+73
tests/plugin_integration.rs
··· 1 + //! Integration tests for the plugin system 2 + //! 3 + //! Note: These tests require a valid WASM plugin to test against. 4 + //! For now, we test the infrastructure without actual WASM execution. 5 + 6 + use happyview::plugin::{LoadedPlugin, PluginInfo, PluginRegistry, PluginSource}; 7 + 8 + #[tokio::test] 9 + async fn test_plugin_registry_crud() { 10 + let registry = PluginRegistry::new(); 11 + 12 + // Create test plugin 13 + let plugin = LoadedPlugin { 14 + info: PluginInfo { 15 + id: "test-plugin".into(), 16 + name: "Test Plugin".into(), 17 + version: "1.0.0".into(), 18 + api_version: "1".into(), 19 + icon_url: None, 20 + required_secrets: vec![], 21 + config_schema: None, 22 + }, 23 + source: PluginSource::File { 24 + path: "/tmp/test".into(), 25 + }, 26 + wasm_bytes: vec![], 27 + }; 28 + 29 + // Register 30 + registry.register(plugin).await; 31 + 32 + // Get 33 + let retrieved = registry.get("test-plugin").await; 34 + assert!(retrieved.is_some()); 35 + assert_eq!(retrieved.unwrap().info.name, "Test Plugin"); 36 + 37 + // List 38 + let all = registry.list().await; 39 + assert_eq!(all.len(), 1); 40 + 41 + // Remove 42 + let removed = registry.remove("test-plugin").await; 43 + assert!(removed.is_some()); 44 + 45 + // Verify removed 46 + assert!(registry.get("test-plugin").await.is_none()); 47 + } 48 + 49 + #[tokio::test] 50 + async fn test_plugin_registry_multiple() { 51 + let registry = PluginRegistry::new(); 52 + 53 + for i in 0..5 { 54 + let plugin = LoadedPlugin { 55 + info: PluginInfo { 56 + id: format!("plugin-{}", i), 57 + name: format!("Plugin {}", i), 58 + version: "1.0.0".into(), 59 + api_version: "1".into(), 60 + icon_url: None, 61 + required_secrets: vec![], 62 + config_schema: None, 63 + }, 64 + source: PluginSource::File { 65 + path: "/tmp/test".into(), 66 + }, 67 + wasm_bytes: vec![], 68 + }; 69 + registry.register(plugin).await; 70 + } 71 + 72 + assert_eq!(registry.list().await.len(), 5); 73 + }