···88[dependencies]
99chrono = "0.4.42"
1010cid = { version = "0.11.1", features = ["serde", "std"] }
1111+langtag = { version = "0.4.0", features = ["serde"] }
1112miette = "7.6.0"
1213multibase = "0.9.1"
1314multihash = "0.19.3"
+1
crates/jacquard-common/src/types.rs
···77pub mod handle;
88pub mod ident;
99pub mod integer;
1010+pub mod language;
1011pub mod link;
1112pub mod nsid;
1213pub mod recordkey;
+112
crates/jacquard-common/src/types/language.rs
···11+use serde::{Deserialize, Deserializer, Serialize, de::Error};
22+use smol_str::{SmolStr, ToSmolStr};
33+use std::fmt;
44+use std::{ops::Deref, str::FromStr};
55+66+use crate::CowStr;
77+88+/// A [Timestamp Identifier].
99+///
1010+/// [Timestamp Identifier]: https://atproto.com/specs/lang
1111+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)]
1212+#[serde(transparent)]
1313+#[repr(transparent)]
1414+pub struct Lang(SmolStr);
1515+1616+impl Lang {
1717+ /// Parses an IETF language tag from the given string.
1818+ pub fn new<T>(lang: &T) -> Result<Self, langtag::InvalidLangTag<&T>>
1919+ where
2020+ T: AsRef<str> + ?Sized,
2121+ {
2222+ let tag = langtag::LangTag::new(lang)?;
2323+ Ok(Lang(SmolStr::new_inline(tag.as_str())))
2424+ }
2525+2626+ /// Infallible constructor for when you *know* the string is a valid IETF language tag.
2727+ /// Will panic on invalid tag. If you're manually decoding atproto records
2828+ /// or API values you know are valid (rather than using serde), this is the one to use.
2929+ /// The From<String> and From<CowStr> impls use the same logic.
3030+ pub fn raw(lang: impl AsRef<str>) -> Self {
3131+ let lang = lang.as_ref();
3232+ let tag = langtag::LangTag::new(lang).expect("valid IETF language tag");
3333+ Lang(SmolStr::new_inline(tag.as_str()))
3434+ }
3535+3636+ /// Infallible constructor for when you *know* the string is a valid IETF language tag.
3737+ /// Marked unsafe because responsibility for upholding the invariant is on the developer.
3838+ pub unsafe fn unchecked(lang: impl AsRef<str>) -> Self {
3939+ let lang = lang.as_ref();
4040+ Self(SmolStr::new_inline(lang))
4141+ }
4242+4343+ /// Returns the LANG as a string slice.
4444+ pub fn as_str(&self) -> &str {
4545+ {
4646+ let this = &self.0;
4747+ this
4848+ }
4949+ }
5050+}
5151+5252+impl FromStr for Lang {
5353+ type Err = SmolStr;
5454+5555+ fn from_str(s: &str) -> Result<Self, Self::Err> {
5656+ Self::new(s).map_err(|e| e.0.to_smolstr())
5757+ }
5858+}
5959+6060+impl<'de> Deserialize<'de> for Lang {
6161+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
6262+ where
6363+ D: Deserializer<'de>,
6464+ {
6565+ let value: &str = Deserialize::deserialize(deserializer)?;
6666+ Self::new(value).map_err(D::Error::custom)
6767+ }
6868+}
6969+7070+impl fmt::Display for Lang {
7171+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7272+ f.write_str(&self.0)
7373+ }
7474+}
7575+7676+impl From<Lang> for String {
7777+ fn from(value: Lang) -> Self {
7878+ value.0.to_string()
7979+ }
8080+}
8181+8282+impl From<Lang> for SmolStr {
8383+ fn from(value: Lang) -> Self {
8484+ value.0
8585+ }
8686+}
8787+8888+impl From<String> for Lang {
8989+ fn from(value: String) -> Self {
9090+ Self::raw(&value)
9191+ }
9292+}
9393+9494+impl<'t> From<CowStr<'t>> for Lang {
9595+ fn from(value: CowStr<'t>) -> Self {
9696+ Self::raw(&value)
9797+ }
9898+}
9999+100100+impl AsRef<str> for Lang {
101101+ fn as_ref(&self) -> &str {
102102+ self.as_str()
103103+ }
104104+}
105105+106106+impl Deref for Lang {
107107+ type Target = str;
108108+109109+ fn deref(&self) -> &Self::Target {
110110+ self.as_str()
111111+ }
112112+}