A better Rust ATProto crate

Language tag string type

Orual d0d85afb 609d712a

Changed files
+423 -11
crates
jacquard-common
+309 -11
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "abnf" 7 + version = "0.13.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a" 10 + dependencies = [ 11 + "abnf-core", 12 + "nom", 13 + ] 14 + 15 + [[package]] 16 + name = "abnf-core" 17 + version = "0.5.0" 18 + source = "registry+https://github.com/rust-lang/crates.io-index" 19 + checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d" 20 + dependencies = [ 21 + "nom", 22 + ] 23 + 24 + [[package]] 6 25 name = "aho-corasick" 7 26 version = "1.1.3" 8 27 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 83 102 checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 84 103 85 104 [[package]] 105 + name = "block-buffer" 106 + version = "0.10.4" 107 + source = "registry+https://github.com/rust-lang/crates.io-index" 108 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 109 + dependencies = [ 110 + "generic-array", 111 + ] 112 + 113 + [[package]] 86 114 name = "borsh" 87 115 version = "1.5.7" 88 116 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 92 120 ] 93 121 94 122 [[package]] 123 + name = "btree-range-map" 124 + version = "0.7.2" 125 + source = "registry+https://github.com/rust-lang/crates.io-index" 126 + checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33" 127 + dependencies = [ 128 + "btree-slab", 129 + "cc-traits", 130 + "range-traits", 131 + "serde", 132 + "slab", 133 + ] 134 + 135 + [[package]] 136 + name = "btree-slab" 137 + version = "0.6.1" 138 + source = "registry+https://github.com/rust-lang/crates.io-index" 139 + checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c" 140 + dependencies = [ 141 + "cc-traits", 142 + "slab", 143 + "smallvec", 144 + ] 145 + 146 + [[package]] 95 147 name = "bumpalo" 96 148 version = "3.19.0" 97 149 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 108 160 ] 109 161 110 162 [[package]] 163 + name = "cc-traits" 164 + version = "2.0.0" 165 + source = "registry+https://github.com/rust-lang/crates.io-index" 166 + checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5" 167 + dependencies = [ 168 + "slab", 169 + ] 170 + 171 + [[package]] 111 172 name = "cfg-if" 112 173 version = "1.0.3" 113 174 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 133 194 ] 134 195 135 196 [[package]] 197 + name = "ciborium" 198 + version = "0.2.2" 199 + source = "registry+https://github.com/rust-lang/crates.io-index" 200 + checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 201 + dependencies = [ 202 + "ciborium-io", 203 + "ciborium-ll", 204 + "serde", 205 + ] 206 + 207 + [[package]] 208 + name = "ciborium-io" 209 + version = "0.2.2" 210 + source = "registry+https://github.com/rust-lang/crates.io-index" 211 + checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 212 + 213 + [[package]] 214 + name = "ciborium-ll" 215 + version = "0.2.2" 216 + source = "registry+https://github.com/rust-lang/crates.io-index" 217 + checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 218 + dependencies = [ 219 + "ciborium-io", 220 + "half", 221 + ] 222 + 223 + [[package]] 136 224 name = "cid" 137 225 version = "0.11.1" 138 226 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 177 265 "heck", 178 266 "proc-macro2", 179 267 "quote", 180 - "syn", 268 + "syn 2.0.106", 181 269 ] 182 270 183 271 [[package]] ··· 208 296 ] 209 297 210 298 [[package]] 299 + name = "cpufeatures" 300 + version = "0.2.17" 301 + source = "registry+https://github.com/rust-lang/crates.io-index" 302 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 303 + dependencies = [ 304 + "libc", 305 + ] 306 + 307 + [[package]] 308 + name = "crunchy" 309 + version = "0.2.4" 310 + source = "registry+https://github.com/rust-lang/crates.io-index" 311 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 312 + 313 + [[package]] 314 + name = "crypto-common" 315 + version = "0.1.6" 316 + source = "registry+https://github.com/rust-lang/crates.io-index" 317 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 318 + dependencies = [ 319 + "generic-array", 320 + "typenum", 321 + ] 322 + 323 + [[package]] 211 324 name = "data-encoding" 212 325 version = "2.9.0" 213 326 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 230 343 checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" 231 344 dependencies = [ 232 345 "data-encoding", 233 - "syn", 346 + "syn 2.0.106", 347 + ] 348 + 349 + [[package]] 350 + name = "digest" 351 + version = "0.10.7" 352 + source = "registry+https://github.com/rust-lang/crates.io-index" 353 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 354 + dependencies = [ 355 + "block-buffer", 356 + "crypto-common", 234 357 ] 235 358 236 359 [[package]] ··· 255 378 ] 256 379 257 380 [[package]] 381 + name = "generic-array" 382 + version = "0.14.7" 383 + source = "registry+https://github.com/rust-lang/crates.io-index" 384 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 385 + dependencies = [ 386 + "typenum", 387 + "version_check", 388 + ] 389 + 390 + [[package]] 391 + name = "half" 392 + version = "2.6.0" 393 + source = "registry+https://github.com/rust-lang/crates.io-index" 394 + checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" 395 + dependencies = [ 396 + "cfg-if", 397 + "crunchy", 398 + ] 399 + 400 + [[package]] 258 401 name = "hashbrown" 259 402 version = "0.16.0" 260 403 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 267 410 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 268 411 269 412 [[package]] 413 + name = "hex_fmt" 414 + version = "0.3.0" 415 + source = "registry+https://github.com/rust-lang/crates.io-index" 416 + checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" 417 + 418 + [[package]] 270 419 name = "iana-time-zone" 271 420 version = "0.1.64" 272 421 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 301 450 ] 302 451 303 452 [[package]] 453 + name = "indoc" 454 + version = "2.0.6" 455 + source = "registry+https://github.com/rust-lang/crates.io-index" 456 + checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" 457 + 458 + [[package]] 304 459 name = "is_terminal_polyfill" 305 460 version = "1.70.1" 306 461 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 326 481 dependencies = [ 327 482 "chrono", 328 483 "cid", 484 + "langtag", 329 485 "miette", 330 486 "multibase", 331 487 "multihash", ··· 334 490 "serde_html_form", 335 491 "serde_json", 336 492 "smol_str", 337 - "thiserror", 493 + "thiserror 2.0.16", 338 494 ] 339 495 340 496 [[package]] ··· 345 501 dependencies = [ 346 502 "once_cell", 347 503 "wasm-bindgen", 504 + ] 505 + 506 + [[package]] 507 + name = "langtag" 508 + version = "0.4.0" 509 + source = "registry+https://github.com/rust-lang/crates.io-index" 510 + checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600" 511 + dependencies = [ 512 + "serde", 513 + "static-regular-grammar", 514 + "thiserror 1.0.69", 348 515 ] 349 516 350 517 [[package]] ··· 384 551 dependencies = [ 385 552 "proc-macro2", 386 553 "quote", 387 - "syn", 554 + "syn 2.0.106", 388 555 ] 389 556 390 557 [[package]] 558 + name = "minimal-lexical" 559 + version = "0.2.1" 560 + source = "registry+https://github.com/rust-lang/crates.io-index" 561 + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 562 + 563 + [[package]] 391 564 name = "multibase" 392 565 version = "0.9.1" 393 566 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 410 583 ] 411 584 412 585 [[package]] 586 + name = "nom" 587 + version = "7.1.3" 588 + source = "registry+https://github.com/rust-lang/crates.io-index" 589 + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 590 + dependencies = [ 591 + "memchr", 592 + "minimal-lexical", 593 + ] 594 + 595 + [[package]] 413 596 name = "num-traits" 414 597 version = "0.2.19" 415 598 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 437 620 checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 438 621 439 622 [[package]] 623 + name = "proc-macro-error" 624 + version = "1.0.4" 625 + source = "registry+https://github.com/rust-lang/crates.io-index" 626 + checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 627 + dependencies = [ 628 + "proc-macro-error-attr", 629 + "proc-macro2", 630 + "quote", 631 + "syn 1.0.109", 632 + "version_check", 633 + ] 634 + 635 + [[package]] 636 + name = "proc-macro-error-attr" 637 + version = "1.0.4" 638 + source = "registry+https://github.com/rust-lang/crates.io-index" 639 + checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 640 + dependencies = [ 641 + "proc-macro2", 642 + "quote", 643 + "version_check", 644 + ] 645 + 646 + [[package]] 440 647 name = "proc-macro2" 441 648 version = "1.0.101" 442 649 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 455 662 ] 456 663 457 664 [[package]] 665 + name = "range-traits" 666 + version = "0.3.2" 667 + source = "registry+https://github.com/rust-lang/crates.io-index" 668 + checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" 669 + 670 + [[package]] 458 671 name = "regex" 459 672 version = "1.11.3" 460 673 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 531 744 dependencies = [ 532 745 "proc-macro2", 533 746 "quote", 534 - "syn", 747 + "syn 2.0.106", 535 748 ] 536 749 537 750 [[package]] ··· 561 774 ] 562 775 563 776 [[package]] 777 + name = "sha2" 778 + version = "0.10.9" 779 + source = "registry+https://github.com/rust-lang/crates.io-index" 780 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 781 + dependencies = [ 782 + "cfg-if", 783 + "cpufeatures", 784 + "digest", 785 + ] 786 + 787 + [[package]] 564 788 name = "shlex" 565 789 version = "1.3.0" 566 790 source = "registry+https://github.com/rust-lang/crates.io-index" 567 791 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 568 792 569 793 [[package]] 794 + name = "slab" 795 + version = "0.4.11" 796 + source = "registry+https://github.com/rust-lang/crates.io-index" 797 + checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 798 + 799 + [[package]] 800 + name = "smallvec" 801 + version = "1.15.1" 802 + source = "registry+https://github.com/rust-lang/crates.io-index" 803 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 804 + 805 + [[package]] 570 806 name = "smol_str" 571 807 version = "0.3.2" 572 808 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 577 813 ] 578 814 579 815 [[package]] 816 + name = "static-regular-grammar" 817 + version = "2.0.2" 818 + source = "registry+https://github.com/rust-lang/crates.io-index" 819 + checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957" 820 + dependencies = [ 821 + "abnf", 822 + "btree-range-map", 823 + "ciborium", 824 + "hex_fmt", 825 + "indoc", 826 + "proc-macro-error", 827 + "proc-macro2", 828 + "quote", 829 + "serde", 830 + "sha2", 831 + "syn 2.0.106", 832 + "thiserror 1.0.69", 833 + ] 834 + 835 + [[package]] 580 836 name = "strsim" 581 837 version = "0.11.1" 582 838 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 584 840 585 841 [[package]] 586 842 name = "syn" 843 + version = "1.0.109" 844 + source = "registry+https://github.com/rust-lang/crates.io-index" 845 + checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 846 + dependencies = [ 847 + "proc-macro2", 848 + "unicode-ident", 849 + ] 850 + 851 + [[package]] 852 + name = "syn" 587 853 version = "2.0.106" 588 854 source = "registry+https://github.com/rust-lang/crates.io-index" 589 855 checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" ··· 595 861 596 862 [[package]] 597 863 name = "thiserror" 864 + version = "1.0.69" 865 + source = "registry+https://github.com/rust-lang/crates.io-index" 866 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 867 + dependencies = [ 868 + "thiserror-impl 1.0.69", 869 + ] 870 + 871 + [[package]] 872 + name = "thiserror" 598 873 version = "2.0.16" 599 874 source = "registry+https://github.com/rust-lang/crates.io-index" 600 875 checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" 601 876 dependencies = [ 602 - "thiserror-impl", 877 + "thiserror-impl 2.0.16", 878 + ] 879 + 880 + [[package]] 881 + name = "thiserror-impl" 882 + version = "1.0.69" 883 + source = "registry+https://github.com/rust-lang/crates.io-index" 884 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 885 + dependencies = [ 886 + "proc-macro2", 887 + "quote", 888 + "syn 2.0.106", 603 889 ] 604 890 605 891 [[package]] ··· 610 896 dependencies = [ 611 897 "proc-macro2", 612 898 "quote", 613 - "syn", 899 + "syn 2.0.106", 614 900 ] 615 901 616 902 [[package]] 903 + name = "typenum" 904 + version = "1.18.0" 905 + source = "registry+https://github.com/rust-lang/crates.io-index" 906 + checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 907 + 908 + [[package]] 617 909 name = "unicode-ident" 618 910 version = "1.0.19" 619 911 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 638 930 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 639 931 640 932 [[package]] 933 + name = "version_check" 934 + version = "0.9.5" 935 + source = "registry+https://github.com/rust-lang/crates.io-index" 936 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 937 + 938 + [[package]] 641 939 name = "wasm-bindgen" 642 940 version = "0.2.104" 643 941 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 660 958 "log", 661 959 "proc-macro2", 662 960 "quote", 663 - "syn", 961 + "syn 2.0.106", 664 962 "wasm-bindgen-shared", 665 963 ] 666 964 ··· 682 980 dependencies = [ 683 981 "proc-macro2", 684 982 "quote", 685 - "syn", 983 + "syn 2.0.106", 686 984 "wasm-bindgen-backend", 687 985 "wasm-bindgen-shared", 688 986 ] ··· 717 1015 dependencies = [ 718 1016 "proc-macro2", 719 1017 "quote", 720 - "syn", 1018 + "syn 2.0.106", 721 1019 ] 722 1020 723 1021 [[package]] ··· 728 1026 dependencies = [ 729 1027 "proc-macro2", 730 1028 "quote", 731 - "syn", 1029 + "syn 2.0.106", 732 1030 ] 733 1031 734 1032 [[package]]
+1
crates/jacquard-common/Cargo.toml
··· 8 8 [dependencies] 9 9 chrono = "0.4.42" 10 10 cid = { version = "0.11.1", features = ["serde", "std"] } 11 + langtag = { version = "0.4.0", features = ["serde"] } 11 12 miette = "7.6.0" 12 13 multibase = "0.9.1" 13 14 multihash = "0.19.3"
+1
crates/jacquard-common/src/types.rs
··· 7 7 pub mod handle; 8 8 pub mod ident; 9 9 pub mod integer; 10 + pub mod language; 10 11 pub mod link; 11 12 pub mod nsid; 12 13 pub mod recordkey;
+112
crates/jacquard-common/src/types/language.rs
··· 1 + use serde::{Deserialize, Deserializer, Serialize, de::Error}; 2 + use smol_str::{SmolStr, ToSmolStr}; 3 + use std::fmt; 4 + use std::{ops::Deref, str::FromStr}; 5 + 6 + use crate::CowStr; 7 + 8 + /// A [Timestamp Identifier]. 9 + /// 10 + /// [Timestamp Identifier]: https://atproto.com/specs/lang 11 + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)] 12 + #[serde(transparent)] 13 + #[repr(transparent)] 14 + pub struct Lang(SmolStr); 15 + 16 + impl Lang { 17 + /// Parses an IETF language tag from the given string. 18 + pub fn new<T>(lang: &T) -> Result<Self, langtag::InvalidLangTag<&T>> 19 + where 20 + T: AsRef<str> + ?Sized, 21 + { 22 + let tag = langtag::LangTag::new(lang)?; 23 + Ok(Lang(SmolStr::new_inline(tag.as_str()))) 24 + } 25 + 26 + /// Infallible constructor for when you *know* the string is a valid IETF language tag. 27 + /// Will panic on invalid tag. If you're manually decoding atproto records 28 + /// or API values you know are valid (rather than using serde), this is the one to use. 29 + /// The From<String> and From<CowStr> impls use the same logic. 30 + pub fn raw(lang: impl AsRef<str>) -> Self { 31 + let lang = lang.as_ref(); 32 + let tag = langtag::LangTag::new(lang).expect("valid IETF language tag"); 33 + Lang(SmolStr::new_inline(tag.as_str())) 34 + } 35 + 36 + /// Infallible constructor for when you *know* the string is a valid IETF language tag. 37 + /// Marked unsafe because responsibility for upholding the invariant is on the developer. 38 + pub unsafe fn unchecked(lang: impl AsRef<str>) -> Self { 39 + let lang = lang.as_ref(); 40 + Self(SmolStr::new_inline(lang)) 41 + } 42 + 43 + /// Returns the LANG as a string slice. 44 + pub fn as_str(&self) -> &str { 45 + { 46 + let this = &self.0; 47 + this 48 + } 49 + } 50 + } 51 + 52 + impl FromStr for Lang { 53 + type Err = SmolStr; 54 + 55 + fn from_str(s: &str) -> Result<Self, Self::Err> { 56 + Self::new(s).map_err(|e| e.0.to_smolstr()) 57 + } 58 + } 59 + 60 + impl<'de> Deserialize<'de> for Lang { 61 + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 62 + where 63 + D: Deserializer<'de>, 64 + { 65 + let value: &str = Deserialize::deserialize(deserializer)?; 66 + Self::new(value).map_err(D::Error::custom) 67 + } 68 + } 69 + 70 + impl fmt::Display for Lang { 71 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 72 + f.write_str(&self.0) 73 + } 74 + } 75 + 76 + impl From<Lang> for String { 77 + fn from(value: Lang) -> Self { 78 + value.0.to_string() 79 + } 80 + } 81 + 82 + impl From<Lang> for SmolStr { 83 + fn from(value: Lang) -> Self { 84 + value.0 85 + } 86 + } 87 + 88 + impl From<String> for Lang { 89 + fn from(value: String) -> Self { 90 + Self::raw(&value) 91 + } 92 + } 93 + 94 + impl<'t> From<CowStr<'t>> for Lang { 95 + fn from(value: CowStr<'t>) -> Self { 96 + Self::raw(&value) 97 + } 98 + } 99 + 100 + impl AsRef<str> for Lang { 101 + fn as_ref(&self) -> &str { 102 + self.as_str() 103 + } 104 + } 105 + 106 + impl Deref for Lang { 107 + type Target = str; 108 + 109 + fn deref(&self) -> &Self::Target { 110 + self.as_str() 111 + } 112 + }