A better Rust ATProto crate

Language tag string type

Orual d0d85afb 609d712a

Changed files
+423 -11
crates
jacquard-common
+309 -11
Cargo.lock
···
version = 4
[[package]]
+
name = "abnf"
+
version = "0.13.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a"
+
dependencies = [
+
"abnf-core",
+
"nom",
+
]
+
+
[[package]]
+
name = "abnf-core"
+
version = "0.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d"
+
dependencies = [
+
"nom",
+
]
+
+
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
[[package]]
+
name = "block-buffer"
+
version = "0.10.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+
dependencies = [
+
"generic-array",
+
]
+
+
[[package]]
name = "borsh"
version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "btree-range-map"
+
version = "0.7.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33"
+
dependencies = [
+
"btree-slab",
+
"cc-traits",
+
"range-traits",
+
"serde",
+
"slab",
+
]
+
+
[[package]]
+
name = "btree-slab"
+
version = "0.6.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c"
+
dependencies = [
+
"cc-traits",
+
"slab",
+
"smallvec",
+
]
+
+
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "cc-traits"
+
version = "2.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5"
+
dependencies = [
+
"slab",
+
]
+
+
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "ciborium"
+
version = "0.2.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+
dependencies = [
+
"ciborium-io",
+
"ciborium-ll",
+
"serde",
+
]
+
+
[[package]]
+
name = "ciborium-io"
+
version = "0.2.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+
[[package]]
+
name = "ciborium-ll"
+
version = "0.2.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+
dependencies = [
+
"ciborium-io",
+
"half",
+
]
+
+
[[package]]
name = "cid"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"heck",
"proc-macro2",
"quote",
-
"syn",
+
"syn 2.0.106",
]
[[package]]
···
]
[[package]]
+
name = "cpufeatures"
+
version = "0.2.17"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+
dependencies = [
+
"libc",
+
]
+
+
[[package]]
+
name = "crunchy"
+
version = "0.2.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+
[[package]]
+
name = "crypto-common"
+
version = "0.1.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+
dependencies = [
+
"generic-array",
+
"typenum",
+
]
+
+
[[package]]
name = "data-encoding"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
dependencies = [
"data-encoding",
-
"syn",
+
"syn 2.0.106",
+
]
+
+
[[package]]
+
name = "digest"
+
version = "0.10.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+
dependencies = [
+
"block-buffer",
+
"crypto-common",
]
[[package]]
···
]
[[package]]
+
name = "generic-array"
+
version = "0.14.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+
dependencies = [
+
"typenum",
+
"version_check",
+
]
+
+
[[package]]
+
name = "half"
+
version = "2.6.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+
dependencies = [
+
"cfg-if",
+
"crunchy",
+
]
+
+
[[package]]
name = "hashbrown"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
+
name = "hex_fmt"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
+
+
[[package]]
name = "iana-time-zone"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "indoc"
+
version = "2.0.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
+
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"chrono",
"cid",
+
"langtag",
"miette",
"multibase",
"multihash",
···
"serde_html_form",
"serde_json",
"smol_str",
-
"thiserror",
+
"thiserror 2.0.16",
]
[[package]]
···
dependencies = [
"once_cell",
"wasm-bindgen",
+
]
+
+
[[package]]
+
name = "langtag"
+
version = "0.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600"
+
dependencies = [
+
"serde",
+
"static-regular-grammar",
+
"thiserror 1.0.69",
]
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn",
+
"syn 2.0.106",
]
[[package]]
+
name = "minimal-lexical"
+
version = "0.2.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+
[[package]]
name = "multibase"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "nom"
+
version = "7.1.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+
dependencies = [
+
"memchr",
+
"minimal-lexical",
+
]
+
+
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
+
name = "proc-macro-error"
+
version = "1.0.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+
dependencies = [
+
"proc-macro-error-attr",
+
"proc-macro2",
+
"quote",
+
"syn 1.0.109",
+
"version_check",
+
]
+
+
[[package]]
+
name = "proc-macro-error-attr"
+
version = "1.0.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+
dependencies = [
+
"proc-macro2",
+
"quote",
+
"version_check",
+
]
+
+
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "range-traits"
+
version = "0.3.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab"
+
+
[[package]]
name = "regex"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
dependencies = [
"proc-macro2",
"quote",
-
"syn",
+
"syn 2.0.106",
]
[[package]]
···
]
[[package]]
+
name = "sha2"
+
version = "0.10.9"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+
dependencies = [
+
"cfg-if",
+
"cpufeatures",
+
"digest",
+
]
+
+
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
+
name = "slab"
+
version = "0.4.11"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+
[[package]]
+
name = "smallvec"
+
version = "1.15.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+
[[package]]
name = "smol_str"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "static-regular-grammar"
+
version = "2.0.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957"
+
dependencies = [
+
"abnf",
+
"btree-range-map",
+
"ciborium",
+
"hex_fmt",
+
"indoc",
+
"proc-macro-error",
+
"proc-macro2",
+
"quote",
+
"serde",
+
"sha2",
+
"syn 2.0.106",
+
"thiserror 1.0.69",
+
]
+
+
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
name = "syn"
+
version = "1.0.109"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+
dependencies = [
+
"proc-macro2",
+
"unicode-ident",
+
]
+
+
[[package]]
+
name = "syn"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
···
[[package]]
name = "thiserror"
+
version = "1.0.69"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+
dependencies = [
+
"thiserror-impl 1.0.69",
+
]
+
+
[[package]]
+
name = "thiserror"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
-
"thiserror-impl",
+
"thiserror-impl 2.0.16",
+
]
+
+
[[package]]
+
name = "thiserror-impl"
+
version = "1.0.69"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+
dependencies = [
+
"proc-macro2",
+
"quote",
+
"syn 2.0.106",
]
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn",
+
"syn 2.0.106",
]
[[package]]
+
name = "typenum"
+
version = "1.18.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
+
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
+
name = "version_check"
+
version = "0.9.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+
[[package]]
name = "wasm-bindgen"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"log",
"proc-macro2",
"quote",
-
"syn",
+
"syn 2.0.106",
"wasm-bindgen-shared",
]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn",
+
"syn 2.0.106",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn",
+
"syn 2.0.106",
]
[[package]]
···
dependencies = [
"proc-macro2",
"quote",
-
"syn",
+
"syn 2.0.106",
]
[[package]]
+1
crates/jacquard-common/Cargo.toml
···
[dependencies]
chrono = "0.4.42"
cid = { version = "0.11.1", features = ["serde", "std"] }
+
langtag = { version = "0.4.0", features = ["serde"] }
miette = "7.6.0"
multibase = "0.9.1"
multihash = "0.19.3"
+1
crates/jacquard-common/src/types.rs
···
pub mod handle;
pub mod ident;
pub mod integer;
+
pub mod language;
pub mod link;
pub mod nsid;
pub mod recordkey;
+112
crates/jacquard-common/src/types/language.rs
···
+
use serde::{Deserialize, Deserializer, Serialize, de::Error};
+
use smol_str::{SmolStr, ToSmolStr};
+
use std::fmt;
+
use std::{ops::Deref, str::FromStr};
+
+
use crate::CowStr;
+
+
/// A [Timestamp Identifier].
+
///
+
/// [Timestamp Identifier]: https://atproto.com/specs/lang
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)]
+
#[serde(transparent)]
+
#[repr(transparent)]
+
pub struct Lang(SmolStr);
+
+
impl Lang {
+
/// Parses an IETF language tag from the given string.
+
pub fn new<T>(lang: &T) -> Result<Self, langtag::InvalidLangTag<&T>>
+
where
+
T: AsRef<str> + ?Sized,
+
{
+
let tag = langtag::LangTag::new(lang)?;
+
Ok(Lang(SmolStr::new_inline(tag.as_str())))
+
}
+
+
/// Infallible constructor for when you *know* the string is a valid IETF language tag.
+
/// Will panic on invalid tag. If you're manually decoding atproto records
+
/// or API values you know are valid (rather than using serde), this is the one to use.
+
/// The From<String> and From<CowStr> impls use the same logic.
+
pub fn raw(lang: impl AsRef<str>) -> Self {
+
let lang = lang.as_ref();
+
let tag = langtag::LangTag::new(lang).expect("valid IETF language tag");
+
Lang(SmolStr::new_inline(tag.as_str()))
+
}
+
+
/// Infallible constructor for when you *know* the string is a valid IETF language tag.
+
/// Marked unsafe because responsibility for upholding the invariant is on the developer.
+
pub unsafe fn unchecked(lang: impl AsRef<str>) -> Self {
+
let lang = lang.as_ref();
+
Self(SmolStr::new_inline(lang))
+
}
+
+
/// Returns the LANG as a string slice.
+
pub fn as_str(&self) -> &str {
+
{
+
let this = &self.0;
+
this
+
}
+
}
+
}
+
+
impl FromStr for Lang {
+
type Err = SmolStr;
+
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
+
Self::new(s).map_err(|e| e.0.to_smolstr())
+
}
+
}
+
+
impl<'de> Deserialize<'de> for Lang {
+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+
where
+
D: Deserializer<'de>,
+
{
+
let value: &str = Deserialize::deserialize(deserializer)?;
+
Self::new(value).map_err(D::Error::custom)
+
}
+
}
+
+
impl fmt::Display for Lang {
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+
f.write_str(&self.0)
+
}
+
}
+
+
impl From<Lang> for String {
+
fn from(value: Lang) -> Self {
+
value.0.to_string()
+
}
+
}
+
+
impl From<Lang> for SmolStr {
+
fn from(value: Lang) -> Self {
+
value.0
+
}
+
}
+
+
impl From<String> for Lang {
+
fn from(value: String) -> Self {
+
Self::raw(&value)
+
}
+
}
+
+
impl<'t> From<CowStr<'t>> for Lang {
+
fn from(value: CowStr<'t>) -> Self {
+
Self::raw(&value)
+
}
+
}
+
+
impl AsRef<str> for Lang {
+
fn as_ref(&self) -> &str {
+
self.as_str()
+
}
+
}
+
+
impl Deref for Lang {
+
type Target = str;
+
+
fn deref(&self) -> &Self::Target {
+
self.as_str()
+
}
+
}