···2727 }
2828 }
29293030- /// Fallible constructor from an existing CowStr, clones and takes
3030+ /// Fallible constructor from an existing CowStr, takes ownership
3131 pub fn from_cowstr(did: CowStr<'d>) -> Result<Did<'d>, &'static str> {
3232 if did.len() > 2048 {
3333 Err("DID too long")
···7272 /// Has to take ownership due to the lifetime constraints of the FromStr trait.
7373 /// Prefer `Did::new()` or `Did::raw` if you want to borrow.
7474 fn from_str(s: &str) -> Result<Self, Self::Err> {
7575- Self::from_cowstr(CowStr::Owned(s.to_compact_string()))
7575+ Self::from_cowstr(CowStr::Borrowed(s).into_static())
7676 }
7777}
7878
crates/jacquard-common/src/handle.rs
This is a binary file and will not be displayed.
+2-9
crates/jacquard-common/src/lib.rs
···11-pub mod aturi;
21#[macro_use]
32pub mod cowstr;
43#[macro_use]
55-pub mod blob;
66-pub mod cid;
44+pub mod into_static;
7588-pub mod did;
99-pub mod handle;
1010-#[macro_use]
1111-pub mod into_static;
1212-pub mod link;
1313-pub mod nsid;
66+pub mod types;
147158pub use cowstr::CowStr;
169pub use into_static::IntoStatic;
···11+pub mod aturi;
22+pub mod blob;
33+pub mod cid;
44+pub mod datetime;
55+pub mod did;
66+pub mod handle;
77+pub mod ident;
88+pub mod integer;
99+pub mod link;
1010+pub mod nsid;
1111+pub mod tid;
+148
crates/jacquard-common/src/types/aturi.rs
···11+use std::fmt;
22+use std::sync::LazyLock;
33+use std::{ops::Deref, str::FromStr};
44+55+use compact_str::ToCompactString;
66+use serde::{Deserialize, Deserializer, Serialize, de::Error};
77+88+use crate::{CowStr, IntoStatic};
99+use regex::Regex;
1010+1111+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Hash)]
1212+#[serde(transparent)]
1313+pub struct AtUri<'a>(CowStr<'a>);
1414+1515+pub static AT_URI_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^$").unwrap());
1616+1717+impl<'a> AtUri<'a> {
1818+ /// Fallible constructor, validates, borrows from input
1919+ pub fn new(uri: &'a str) -> Result<Self, &'static str> {
2020+ if uri.len() > 2048 {
2121+ Err("AT_URI too long")
2222+ } else if !AT_URI_REGEX.is_match(uri) {
2323+ Err("Invalid AT_URI")
2424+ } else {
2525+ Ok(Self(CowStr::Borrowed(uri)))
2626+ }
2727+ }
2828+2929+ /// Fallible constructor from an existing CowStr, clones and takes
3030+ pub fn from_cowstr(uri: CowStr<'a>) -> Result<AtUri<'a>, &'static str> {
3131+ if uri.len() > 2048 {
3232+ Err("AT_URI too long")
3333+ } else if !AT_URI_REGEX.is_match(&uri) {
3434+ Err("Invalid AT_URI")
3535+ } else {
3636+ Ok(Self(uri.into_static()))
3737+ }
3838+ }
3939+4040+ /// Infallible constructor for when you *know* the string slice is a valid at:// uri.
4141+ /// Will panic on invalid URIs. If you're manually decoding atproto records
4242+ /// or API values you know are valid (rather than using serde), this is the one to use.
4343+ /// The From<String> and From<CowStr> impls use the same logic.
4444+ pub fn raw(uri: &'a str) -> Self {
4545+ if uri.len() > 2048 {
4646+ panic!("AT_URI too long")
4747+ } else if !AT_URI_REGEX.is_match(uri) {
4848+ panic!("Invalid AT_URI")
4949+ } else {
5050+ Self(CowStr::Borrowed(uri))
5151+ }
5252+ }
5353+5454+ /// Infallible constructor for when you *know* the string is a valid AT_URI.
5555+ /// Marked unsafe because responsibility for upholding the invariant is on the developer.
5656+ pub unsafe fn unchecked(uri: &'a str) -> Self {
5757+ Self(CowStr::Borrowed(uri))
5858+ }
5959+6060+ pub fn as_str(&self) -> &str {
6161+ {
6262+ let this = &self.0;
6363+ this
6464+ }
6565+ }
6666+}
6767+6868+impl FromStr for AtUri<'_> {
6969+ type Err = &'static str;
7070+7171+ /// Has to take ownership due to the lifetime constraints of the FromStr trait.
7272+ /// Prefer `AtUri::new()` or `AtUri::raw` if you want to borrow.
7373+ fn from_str(s: &str) -> Result<Self, Self::Err> {
7474+ Self::from_cowstr(CowStr::Owned(s.to_compact_string()))
7575+ }
7676+}
7777+7878+impl<'ae> Deserialize<'ae> for AtUri<'ae> {
7979+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
8080+ where
8181+ D: Deserializer<'ae>,
8282+ {
8383+ let value = Deserialize::deserialize(deserializer)?;
8484+ Self::new(value).map_err(D::Error::custom)
8585+ }
8686+}
8787+8888+impl fmt::Display for AtUri<'_> {
8989+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
9090+ f.write_str(&self.0)
9191+ }
9292+}
9393+9494+impl<'a> From<AtUri<'a>> for String {
9595+ fn from(value: AtUri<'a>) -> Self {
9696+ value.0.to_string()
9797+ }
9898+}
9999+100100+impl<'s> From<&'s AtUri<'_>> for &'s str {
101101+ fn from(value: &'s AtUri<'_>) -> Self {
102102+ value.0.as_ref()
103103+ }
104104+}
105105+106106+impl<'a> From<AtUri<'a>> for CowStr<'a> {
107107+ fn from(value: AtUri<'a>) -> Self {
108108+ value.0
109109+ }
110110+}
111111+112112+impl From<String> for AtUri<'static> {
113113+ fn from(value: String) -> Self {
114114+ if value.len() > 2048 {
115115+ panic!("AT_URI too long")
116116+ } else if !AT_URI_REGEX.is_match(&value) {
117117+ panic!("Invalid AT_URI")
118118+ } else {
119119+ Self(CowStr::Owned(value.to_compact_string()))
120120+ }
121121+ }
122122+}
123123+124124+impl<'a> From<CowStr<'a>> for AtUri<'a> {
125125+ fn from(value: CowStr<'a>) -> Self {
126126+ if value.len() > 2048 {
127127+ panic!("AT_URI too long")
128128+ } else if !AT_URI_REGEX.is_match(&value) {
129129+ panic!("Invalid AT_URI")
130130+ } else {
131131+ Self(value)
132132+ }
133133+ }
134134+}
135135+136136+impl AsRef<str> for AtUri<'_> {
137137+ fn as_ref(&self) -> &str {
138138+ self.as_str()
139139+ }
140140+}
141141+142142+impl Deref for AtUri<'_> {
143143+ type Target = str;
144144+145145+ fn deref(&self) -> &Self::Target {
146146+ self.as_str()
147147+ }
148148+}
+166
crates/jacquard-common/src/types/datetime.rs
···11+use std::sync::LazyLock;
22+use std::{cmp, str::FromStr};
33+44+use chrono::DurationRound;
55+use compact_str::ToCompactString;
66+use serde::Serializer;
77+use serde::{Deserialize, Deserializer, Serialize, de::Error};
88+99+use crate::{CowStr, IntoStatic};
1010+use regex::Regex;
1111+1212+pub static ISO8601_REGEX: LazyLock<Regex> = LazyLock::new(|| {
1313+ Regex::new(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|(\+[0-9]{2}|\-[0-9][1-9]):[0-9]{2})$").unwrap()
1414+});
1515+1616+/// A Lexicon timestamp.
1717+#[derive(Clone, Debug, Eq)]
1818+pub struct Datetime {
1919+ /// Serialized form. Preserved during parsing to ensure round-trip re-serialization.
2020+ serialized: CowStr<'static>,
2121+ /// Parsed form.
2222+ dt: chrono::DateTime<chrono::FixedOffset>,
2323+}
2424+2525+impl PartialEq for Datetime {
2626+ fn eq(&self, other: &Self) -> bool {
2727+ self.dt == other.dt
2828+ }
2929+}
3030+3131+impl Ord for Datetime {
3232+ fn cmp(&self, other: &Self) -> cmp::Ordering {
3333+ self.dt.cmp(&other.dt)
3434+ }
3535+}
3636+3737+impl PartialOrd for Datetime {
3838+ fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
3939+ Some(self.cmp(other))
4040+ }
4141+}
4242+4343+impl Datetime {
4444+ /// Returns a `Datetime` which corresponds to the current date and time in UTC.
4545+ ///
4646+ /// The timestamp uses microsecond precision.
4747+ pub fn now() -> Self {
4848+ Self::new(chrono::Utc::now().fixed_offset())
4949+ }
5050+5151+ /// Constructs a new Lexicon timestamp.
5252+ ///
5353+ /// The timestamp is rounded to microsecond precision.
5454+ pub fn new(dt: chrono::DateTime<chrono::FixedOffset>) -> Self {
5555+ let dt = dt
5656+ .duration_round(chrono::Duration::microseconds(1))
5757+ .expect("delta does not exceed limits");
5858+ // This serialization format is compatible with ISO 8601.
5959+ let serialized = CowStr::Owned(
6060+ dt.to_rfc3339_opts(chrono::SecondsFormat::Micros, true)
6161+ .to_compact_string(),
6262+ );
6363+ Self { serialized, dt }
6464+ }
6565+6666+ /// Infallibly parses a new Lexicon timestamp from a compatible str reference
6767+ ///
6868+ /// Panics if invalid. Use the fallible trait implementations or deserialize for input
6969+ /// you cannot reasonably trust to be properly formatted.
7070+ pub fn raw_str(s: impl AsRef<str>) -> Self {
7171+ let s = s.as_ref();
7272+ if ISO8601_REGEX.is_match(s) {
7373+ let dt = chrono::DateTime::parse_from_rfc3339(s).expect("valid ISO8601 time string");
7474+ Self {
7575+ serialized: CowStr::Borrowed(s).into_static(),
7676+ dt,
7777+ }
7878+ } else {
7979+ panic!("atproto datetime should be valid ISO8601")
8080+ }
8181+ }
8282+8383+ /// Extracts a string slice containing the entire `Datetime`.
8484+ #[inline]
8585+ #[must_use]
8686+ pub fn as_str(&self) -> &str {
8787+ self.serialized.as_ref()
8888+ }
8989+}
9090+9191+impl FromStr for Datetime {
9292+ type Err = chrono::ParseError;
9393+9494+ fn from_str(s: &str) -> Result<Self, Self::Err> {
9595+ // The `chrono` crate only supports RFC 3339 parsing, but Lexicon restricts
9696+ // datetimes to the subset that is also valid under ISO 8601. Apply a regex that
9797+ // validates enough of the relevant ISO 8601 format that the RFC 3339 parser can
9898+ // do the rest.
9999+ if ISO8601_REGEX.is_match(s) {
100100+ let dt = chrono::DateTime::parse_from_rfc3339(s)?;
101101+ Ok(Self {
102102+ serialized: CowStr::Borrowed(s).into_static(),
103103+ dt,
104104+ })
105105+ } else {
106106+ // Simulate an invalid `ParseError`.
107107+ Err(chrono::DateTime::parse_from_rfc3339("invalid").expect_err("invalid"))
108108+ }
109109+ }
110110+}
111111+112112+impl<'de> Deserialize<'de> for Datetime {
113113+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114114+ where
115115+ D: Deserializer<'de>,
116116+ {
117117+ let value: String = Deserialize::deserialize(deserializer)?;
118118+ Self::from_str(&value).map_err(D::Error::custom)
119119+ }
120120+}
121121+impl Serialize for Datetime {
122122+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
123123+ where
124124+ S: Serializer,
125125+ {
126126+ serializer.serialize_str(&self.serialized)
127127+ }
128128+}
129129+130130+impl AsRef<chrono::DateTime<chrono::FixedOffset>> for Datetime {
131131+ fn as_ref(&self) -> &chrono::DateTime<chrono::FixedOffset> {
132132+ &self.dt
133133+ }
134134+}
135135+136136+impl TryFrom<String> for Datetime {
137137+ type Error = chrono::ParseError;
138138+ fn try_from(value: String) -> Result<Self, Self::Error> {
139139+ if ISO8601_REGEX.is_match(&value) {
140140+ let dt = chrono::DateTime::parse_from_rfc3339(&value)?;
141141+ Ok(Self {
142142+ serialized: CowStr::Owned(value.to_compact_string()),
143143+ dt,
144144+ })
145145+ } else {
146146+ // Simulate an invalid `ParseError`.
147147+ Err(chrono::DateTime::parse_from_rfc3339("invalid").expect_err("invalid"))
148148+ }
149149+ }
150150+}
151151+152152+impl TryFrom<CowStr<'_>> for Datetime {
153153+ type Error = chrono::ParseError;
154154+ fn try_from(value: CowStr<'_>) -> Result<Self, Self::Error> {
155155+ if ISO8601_REGEX.is_match(&value) {
156156+ let dt = chrono::DateTime::parse_from_rfc3339(&value)?;
157157+ Ok(Self {
158158+ serialized: value.into_static(),
159159+ dt,
160160+ })
161161+ } else {
162162+ // Simulate an invalid `ParseError`.
163163+ Err(chrono::DateTime::parse_from_rfc3339("invalid").expect_err("invalid"))
164164+ }
165165+ }
166166+}
+160
crates/jacquard-common/src/types/handle.rs
···11+use std::fmt;
22+use std::sync::LazyLock;
33+use std::{ops::Deref, str::FromStr};
44+55+use compact_str::ToCompactString;
66+use serde::{Deserialize, Deserializer, Serialize, de::Error};
77+88+use crate::{CowStr, IntoStatic};
99+use regex::Regex;
1010+1111+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Hash)]
1212+#[serde(transparent)]
1313+pub struct Handle<'h>(CowStr<'h>);
1414+1515+pub static HANDLE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
1616+ Regex::new(r"^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$").unwrap()
1717+});
1818+1919+impl<'h> Handle<'h> {
2020+ /// Fallible constructor, validates, borrows from input
2121+ ///
2222+ /// Accepts (and strips) preceding '@' if present
2323+ pub fn new(handle: &'h str) -> Result<Self, &'static str> {
2424+ let handle = handle.strip_prefix('@').unwrap_or(handle);
2525+ if handle.len() > 2048 {
2626+ Err("handle too long")
2727+ } else if !HANDLE_REGEX.is_match(handle) {
2828+ Err("Invalid handle")
2929+ } else {
3030+ Ok(Self(CowStr::Borrowed(handle)))
3131+ }
3232+ }
3333+3434+ /// Fallible constructor from an existing CowStr, takes ownership
3535+ ///
3636+ /// Accepts (and strips) preceding '@' if present
3737+ pub fn from_cowstr(handle: CowStr<'h>) -> Result<Handle<'h>, &'static str> {
3838+ let handle = if let Some(handle) = handle.strip_prefix('@') {
3939+ CowStr::Borrowed(handle)
4040+ } else {
4141+ handle
4242+ };
4343+ if handle.len() > 2048 {
4444+ Err("handle too long")
4545+ } else if !HANDLE_REGEX.is_match(&handle) {
4646+ Err("Invalid handle")
4747+ } else {
4848+ Ok(Self(handle.into_static()))
4949+ }
5050+ }
5151+5252+ /// Infallible constructor for when you *know* the string is a valid handle.
5353+ /// Will panic on invalid handles. If you're manually decoding atproto records
5454+ /// or API values you know are valid (rather than using serde), this is the one to use.
5555+ /// The From<String> and From<CowStr> impls use the same logic.
5656+ ///
5757+ /// Accepts (and strips) preceding '@' if present
5858+ pub fn raw(handle: &'h str) -> Self {
5959+ let handle = handle.strip_prefix('@').unwrap_or(handle);
6060+ if handle.len() > 2048 {
6161+ panic!("handle too long")
6262+ } else if !HANDLE_REGEX.is_match(handle) {
6363+ panic!("Invalid handle")
6464+ } else {
6565+ Self(CowStr::Borrowed(handle))
6666+ }
6767+ }
6868+6969+ /// Infallible constructor for when you *know* the string is a valid handle.
7070+ /// Marked unsafe because responsibility for upholding the invariant is on the developer.
7171+ ///
7272+ /// Accepts (and strips) preceding '@' if present
7373+ pub unsafe fn unchecked(handle: &'h str) -> Self {
7474+ let handle = handle.strip_prefix('@').unwrap_or(handle);
7575+ Self(CowStr::Borrowed(handle))
7676+ }
7777+7878+ pub fn as_str(&self) -> &str {
7979+ {
8080+ let this = &self.0;
8181+ this
8282+ }
8383+ }
8484+}
8585+8686+impl FromStr for Handle<'_> {
8787+ type Err = &'static str;
8888+8989+ /// Has to take ownership due to the lifetime constraints of the FromStr trait.
9090+ /// Prefer `Handle::new()` or `Handle::raw` if you want to borrow.
9191+ fn from_str(s: &str) -> Result<Self, Self::Err> {
9292+ Self::from_cowstr(CowStr::Borrowed(s).into_static())
9393+ }
9494+}
9595+9696+impl<'de> Deserialize<'de> for Handle<'de> {
9797+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
9898+ where
9999+ D: Deserializer<'de>,
100100+ {
101101+ let value = Deserialize::deserialize(deserializer)?;
102102+ Self::new(value).map_err(D::Error::custom)
103103+ }
104104+}
105105+106106+impl fmt::Display for Handle<'_> {
107107+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108108+ write!(f, "@{}", self.0)
109109+ }
110110+}
111111+112112+impl<'h> From<Handle<'h>> for String {
113113+ fn from(value: Handle<'h>) -> Self {
114114+ value.0.to_string()
115115+ }
116116+}
117117+118118+impl<'h> From<Handle<'h>> for CowStr<'h> {
119119+ fn from(value: Handle<'h>) -> Self {
120120+ value.0
121121+ }
122122+}
123123+124124+impl From<String> for Handle<'static> {
125125+ fn from(value: String) -> Self {
126126+ if value.len() > 2048 {
127127+ panic!("handle too long")
128128+ } else if !HANDLE_REGEX.is_match(&value) {
129129+ panic!("Invalid handle")
130130+ } else {
131131+ Self(CowStr::Owned(value.to_compact_string()))
132132+ }
133133+ }
134134+}
135135+136136+impl<'h> From<CowStr<'h>> for Handle<'h> {
137137+ fn from(value: CowStr<'h>) -> Self {
138138+ if value.len() > 2048 {
139139+ panic!("handle too long")
140140+ } else if !HANDLE_REGEX.is_match(&value) {
141141+ panic!("Invalid handle")
142142+ } else {
143143+ Self(value)
144144+ }
145145+ }
146146+}
147147+148148+impl AsRef<str> for Handle<'_> {
149149+ fn as_ref(&self) -> &str {
150150+ self.as_str()
151151+ }
152152+}
153153+154154+impl Deref for Handle<'_> {
155155+ type Target = str;
156156+157157+ fn deref(&self) -> &Self::Target {
158158+ self.as_str()
159159+ }
160160+}
+148
crates/jacquard-common/src/types/ident.rs
···11+use crate::types::did::Did;
22+use crate::types::handle::Handle;
33+use std::fmt;
44+use std::str::FromStr;
55+66+use serde::{Deserialize, Serialize};
77+88+use crate::CowStr;
99+1010+/// An AT Protocol identifier.
1111+#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, Hash)]
1212+#[serde(untagged)]
1313+pub enum AtIdentifier<'i> {
1414+ #[serde(borrow)]
1515+ Did(Did<'i>),
1616+ Handle(Handle<'i>),
1717+}
1818+1919+impl<'i> AtIdentifier<'i> {
2020+ /// Fallible constructor, validates, borrows from input
2121+ pub fn new(ident: &'i str) -> Result<Self, &'static str> {
2222+ if let Ok(did) = ident.parse() {
2323+ Ok(AtIdentifier::Did(did))
2424+ } else {
2525+ ident.parse().map(AtIdentifier::Handle)
2626+ }
2727+ }
2828+2929+ /// Fallible constructor from an existing CowStr, borrows
3030+ pub fn from_cowstr(ident: CowStr<'i>) -> Result<AtIdentifier<'i>, &'static str> {
3131+ if let Ok(did) = ident.parse() {
3232+ Ok(AtIdentifier::Did(did))
3333+ } else {
3434+ ident.parse().map(AtIdentifier::Handle)
3535+ }
3636+ }
3737+3838+ /// Infallible constructor for when you *know* the string is a valid identifier.
3939+ /// Will panic on invalid identifiers. If you're manually decoding atproto records
4040+ /// or API values you know are valid (rather than using serde), this is the one to use.
4141+ /// The From<String> and From<CowStr> impls use the same logic.
4242+ pub fn raw(ident: &'i str) -> Self {
4343+ if let Ok(did) = ident.parse() {
4444+ AtIdentifier::Did(did)
4545+ } else {
4646+ ident
4747+ .parse()
4848+ .map(AtIdentifier::Handle)
4949+ .expect("valid handle")
5050+ }
5151+ }
5252+5353+ /// Infallible constructor for when you *know* the string is a valid identifier.
5454+ /// Marked unsafe because responsibility for upholding the invariant is on the developer.
5555+ ///
5656+ /// Will validate DIDs, but will treat anything else as a valid handle
5757+ pub unsafe fn unchecked(ident: &'i str) -> Self {
5858+ if let Ok(did) = ident.parse() {
5959+ AtIdentifier::Did(did)
6060+ } else {
6161+ unsafe { AtIdentifier::Handle(Handle::unchecked(ident)) }
6262+ }
6363+ }
6464+6565+ pub fn as_str(&self) -> &str {
6666+ match self {
6767+ AtIdentifier::Did(did) => did.as_str(),
6868+ AtIdentifier::Handle(handle) => handle.as_str(),
6969+ }
7070+ }
7171+}
7272+7373+impl<'i> From<Did<'i>> for AtIdentifier<'i> {
7474+ fn from(did: Did<'i>) -> Self {
7575+ AtIdentifier::Did(did)
7676+ }
7777+}
7878+7979+impl<'i> From<Handle<'i>> for AtIdentifier<'i> {
8080+ fn from(handle: Handle<'i>) -> Self {
8181+ AtIdentifier::Handle(handle)
8282+ }
8383+}
8484+8585+impl FromStr for AtIdentifier<'_> {
8686+ type Err = &'static str;
8787+8888+ fn from_str(s: &str) -> Result<Self, Self::Err> {
8989+ if let Ok(did) = s.parse() {
9090+ Ok(AtIdentifier::Did(did))
9191+ } else {
9292+ s.parse().map(AtIdentifier::Handle)
9393+ }
9494+ }
9595+}
9696+9797+impl fmt::Display for AtIdentifier<'_> {
9898+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
9999+ match self {
100100+ AtIdentifier::Did(did) => did.fmt(f),
101101+ AtIdentifier::Handle(handle) => handle.fmt(f),
102102+ }
103103+ }
104104+}
105105+106106+impl From<String> for AtIdentifier<'static> {
107107+ fn from(value: String) -> Self {
108108+ if let Ok(did) = value.parse() {
109109+ AtIdentifier::Did(did)
110110+ } else {
111111+ value
112112+ .parse()
113113+ .map(AtIdentifier::Handle)
114114+ .expect("valid handle")
115115+ }
116116+ }
117117+}
118118+119119+impl<'i> From<CowStr<'i>> for AtIdentifier<'i> {
120120+ fn from(value: CowStr<'i>) -> Self {
121121+ if let Ok(did) = value.parse() {
122122+ AtIdentifier::Did(did)
123123+ } else {
124124+ value
125125+ .parse()
126126+ .map(AtIdentifier::Handle)
127127+ .expect("valid handle")
128128+ }
129129+ }
130130+}
131131+132132+impl<'i> From<AtIdentifier<'i>> for String {
133133+ fn from(value: AtIdentifier) -> Self {
134134+ match value {
135135+ AtIdentifier::Did(did) => did.into(),
136136+ AtIdentifier::Handle(handle) => handle.into(),
137137+ }
138138+ }
139139+}
140140+141141+impl AsRef<str> for AtIdentifier<'_> {
142142+ fn as_ref(&self) -> &str {
143143+ match self {
144144+ AtIdentifier::Did(did) => did.as_ref(),
145145+ AtIdentifier::Handle(handle) => handle.as_ref(),
146146+ }
147147+ }
148148+}
+326
crates/jacquard-common/src/types/integer.rs
···11+//! Lexicon integer types with minimum or maximum acceptable values.
22+//! Copied from [atrium](https://github.com/atrium-rs/atrium/blob/main/atrium-api/src/types/integer.rs), because this they got right
33+44+use std::num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64};
55+use std::str::FromStr;
66+77+use serde::{Deserialize, de::Error};
88+99+macro_rules! uint {
1010+ ($primitive:ident, $nz:ident, $lim:ident, $lim_nz:ident, $bounded:ident) => {
1111+ /// An unsigned integer with a maximum value of `MAX`.
1212+ #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, Hash)]
1313+ #[repr(transparent)]
1414+ #[serde(transparent)]
1515+ pub struct $lim<const MAX: $primitive>($primitive);
1616+1717+ impl<const MAX: $primitive> $lim<MAX> {
1818+ /// The smallest value that can be represented by this limited integer type.
1919+ pub const MIN: Self = Self(<$primitive>::MIN);
2020+2121+ /// The largest value that can be represented by this limited integer type.
2222+ pub const MAX: Self = Self(MAX);
2323+2424+ fn new(value: $primitive) -> Result<Self, String> {
2525+ if value > MAX {
2626+ Err(format!("value is greater than {}", MAX))
2727+ } else {
2828+ Ok(Self(value))
2929+ }
3030+ }
3131+ }
3232+3333+ impl<const MAX: $primitive> FromStr for $lim<MAX> {
3434+ type Err = String;
3535+3636+ fn from_str(src: &str) -> Result<Self, Self::Err> {
3737+ Self::new(src.parse::<$primitive>().map_err(|e| e.to_string())?)
3838+ }
3939+ }
4040+4141+ impl<const MAX: $primitive> TryFrom<$primitive> for $lim<MAX> {
4242+ type Error = String;
4343+4444+ fn try_from(value: $primitive) -> Result<Self, Self::Error> {
4545+ Self::new(value)
4646+ }
4747+ }
4848+4949+ impl<'de, const MAX: $primitive> Deserialize<'de> for $lim<MAX> {
5050+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
5151+ where
5252+ D: serde::Deserializer<'de>,
5353+ {
5454+ Self::new(Deserialize::deserialize(deserializer)?).map_err(D::Error::custom)
5555+ }
5656+ }
5757+5858+ impl<const MAX: $primitive> From<$lim<MAX>> for $primitive {
5959+ fn from(value: $lim<MAX>) -> Self {
6060+ value.0
6161+ }
6262+ }
6363+6464+ /// An unsigned integer with a minimum value of 1 and a maximum value of `MAX`.
6565+ #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, Hash)]
6666+ #[repr(transparent)]
6767+ #[serde(transparent)]
6868+ pub struct $lim_nz<const MAX: $primitive>($nz);
6969+7070+ impl<const MAX: $primitive> $lim_nz<MAX> {
7171+ /// The smallest value that can be represented by this limited non-zero
7272+ /// integer type.
7373+ pub const MIN: Self = Self($nz::MIN);
7474+7575+ /// The largest value that can be represented by this limited non-zero integer
7676+ /// type.
7777+ pub const MAX: Self = Self(unsafe { $nz::new_unchecked(MAX) });
7878+7979+ fn new(value: $primitive) -> Result<Self, String> {
8080+ if value > MAX {
8181+ Err(format!("value is greater than {}", MAX))
8282+ } else if let Some(value) = $nz::new(value) {
8383+ Ok(Self(value))
8484+ } else {
8585+ Err("value is zero".into())
8686+ }
8787+ }
8888+ }
8989+9090+ impl<const MAX: $primitive> FromStr for $lim_nz<MAX> {
9191+ type Err = String;
9292+9393+ fn from_str(src: &str) -> Result<Self, Self::Err> {
9494+ Self::new(src.parse::<$primitive>().map_err(|e| e.to_string())?)
9595+ }
9696+ }
9797+9898+ impl<const MAX: $primitive> TryFrom<$primitive> for $lim_nz<MAX> {
9999+ type Error = String;
100100+101101+ fn try_from(value: $primitive) -> Result<Self, Self::Error> {
102102+ Self::new(value)
103103+ }
104104+ }
105105+106106+ impl<'de, const MAX: $primitive> Deserialize<'de> for $lim_nz<MAX> {
107107+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108108+ where
109109+ D: serde::Deserializer<'de>,
110110+ {
111111+ Self::new(Deserialize::deserialize(deserializer)?).map_err(D::Error::custom)
112112+ }
113113+ }
114114+115115+ impl<const MAX: $primitive> From<$lim_nz<MAX>> for $nz {
116116+ fn from(value: $lim_nz<MAX>) -> Self {
117117+ value.0
118118+ }
119119+ }
120120+121121+ impl<const MAX: $primitive> From<$lim_nz<MAX>> for $primitive {
122122+ fn from(value: $lim_nz<MAX>) -> Self {
123123+ value.0.into()
124124+ }
125125+ }
126126+127127+ /// An unsigned integer with a minimum value of `MIN` and a maximum value of `MAX`.
128128+ ///
129129+ /// `MIN` must be non-zero.
130130+ #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, Hash)]
131131+ #[repr(transparent)]
132132+ #[serde(transparent)]
133133+ pub struct $bounded<const MIN: $primitive, const MAX: $primitive>($nz);
134134+135135+ impl<const MIN: $primitive, const MAX: $primitive> $bounded<MIN, MAX> {
136136+ /// The smallest value that can be represented by this bounded integer type.
137137+ pub const MIN: Self = Self(unsafe { $nz::new_unchecked(MIN) });
138138+139139+ /// The largest value that can be represented by this bounded integer type.
140140+ pub const MAX: Self = Self(unsafe { $nz::new_unchecked(MAX) });
141141+142142+ fn new(value: $primitive) -> Result<Self, String> {
143143+ if value < MIN {
144144+ Err(format!("value is less than {}", MIN))
145145+ } else if value > MAX {
146146+ Err(format!("value is greater than {}", MAX))
147147+ } else if let Some(value) = $nz::new(value) {
148148+ Ok(Self(value))
149149+ } else {
150150+ Err("value is zero".into())
151151+ }
152152+ }
153153+ }
154154+155155+ impl<const MIN: $primitive, const MAX: $primitive> TryFrom<$primitive>
156156+ for $bounded<MIN, MAX>
157157+ {
158158+ type Error = String;
159159+160160+ fn try_from(value: $primitive) -> Result<Self, Self::Error> {
161161+ Self::new(value)
162162+ }
163163+ }
164164+165165+ impl<const MIN: $primitive, const MAX: $primitive> FromStr for $bounded<MIN, MAX> {
166166+ type Err = String;
167167+168168+ fn from_str(src: &str) -> Result<Self, Self::Err> {
169169+ Self::new(src.parse::<$primitive>().map_err(|e| e.to_string())?)
170170+ }
171171+ }
172172+173173+ impl<'de, const MIN: $primitive, const MAX: $primitive> Deserialize<'de>
174174+ for $bounded<MIN, MAX>
175175+ {
176176+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
177177+ where
178178+ D: serde::Deserializer<'de>,
179179+ {
180180+ Self::new(Deserialize::deserialize(deserializer)?).map_err(D::Error::custom)
181181+ }
182182+ }
183183+184184+ impl<const MIN: $primitive, const MAX: $primitive> From<$bounded<MIN, MAX>> for $nz {
185185+ fn from(value: $bounded<MIN, MAX>) -> Self {
186186+ value.0
187187+ }
188188+ }
189189+190190+ impl<const MIN: $primitive, const MAX: $primitive> From<$bounded<MIN, MAX>> for $primitive {
191191+ fn from(value: $bounded<MIN, MAX>) -> Self {
192192+ value.0.into()
193193+ }
194194+ }
195195+ };
196196+}
197197+198198+uint!(u8, NonZeroU8, LimitedU8, LimitedNonZeroU8, BoundedU8);
199199+uint!(u16, NonZeroU16, LimitedU16, LimitedNonZeroU16, BoundedU16);
200200+uint!(u32, NonZeroU32, LimitedU32, LimitedNonZeroU32, BoundedU32);
201201+uint!(u64, NonZeroU64, LimitedU64, LimitedNonZeroU64, BoundedU64);
202202+203203+#[cfg(test)]
204204+mod tests {
205205+ use super::*;
206206+207207+ #[test]
208208+ fn u8_min_max() {
209209+ assert_eq!(Ok(LimitedU8::<10>::MIN), 0.try_into());
210210+ assert_eq!(Ok(LimitedU8::<10>::MAX), 10.try_into());
211211+ assert_eq!(Ok(LimitedNonZeroU8::<10>::MIN), 1.try_into());
212212+ assert_eq!(Ok(LimitedNonZeroU8::<10>::MAX), 10.try_into());
213213+ assert_eq!(Ok(BoundedU8::<7, 10>::MIN), 7.try_into());
214214+ assert_eq!(Ok(BoundedU8::<7, 10>::MAX), 10.try_into());
215215+ }
216216+217217+ #[test]
218218+ fn u8_from_str() {
219219+ {
220220+ type LU8 = LimitedU8<10>;
221221+ assert_eq!(Ok(LU8::MIN), "0".parse());
222222+ assert_eq!(Ok(LU8::MAX), "10".parse());
223223+ assert_eq!(Err("value is greater than 10".into()), "11".parse::<LU8>());
224224+ }
225225+ {
226226+ type LU8 = LimitedNonZeroU8<10>;
227227+ assert_eq!(Ok(LU8::MIN), "1".parse());
228228+ assert_eq!(Ok(LU8::MAX), "10".parse());
229229+ assert_eq!(Err("value is greater than 10".into()), "11".parse::<LU8>());
230230+ }
231231+ {
232232+ type BU8 = BoundedU8<7, 10>;
233233+ assert_eq!(Err("value is less than 7".into()), "6".parse::<BU8>());
234234+ assert_eq!(Ok(BU8::MIN), "7".parse());
235235+ assert_eq!(Ok(BU8::MAX), "10".parse());
236236+ assert_eq!(Err("value is greater than 10".into()), "11".parse::<BU8>());
237237+ }
238238+ }
239239+240240+ #[test]
241241+ fn deserialize_u8_from_str() {
242242+ {
243243+ #[derive(Deserialize, Debug)]
244244+ struct Foo {
245245+ bar: LimitedU8<10>,
246246+ }
247247+248248+ match serde_json::from_str::<Foo>(r#"{"bar": 0}"#) {
249249+ Ok(foo) => assert_eq!(foo.bar, LimitedU8::<10>::MIN),
250250+ Err(e) => panic!("failed to deserialize: {e}"),
251251+ }
252252+ match serde_json::from_str::<Foo>(r#"{"bar": "0"}"#) {
253253+ Ok(_) => panic!("deserialization should fail"),
254254+ Err(e) => assert!(e.to_string().contains("invalid type: string")),
255255+ }
256256+ match serde_html_form::from_str::<Foo>(r#"bar=0"#) {
257257+ Ok(foo) => assert_eq!(foo.bar, LimitedU8::<10>::MIN),
258258+ Err(e) => panic!("failed to deserialize: {e}"),
259259+ }
260260+ match serde_html_form::from_str::<Foo>(r#"bar=10"#) {
261261+ Ok(foo) => assert_eq!(foo.bar, LimitedU8::<10>::MAX),
262262+ Err(e) => panic!("failed to deserialize: {e}"),
263263+ }
264264+ match serde_html_form::from_str::<Foo>(r#"bar=11"#) {
265265+ Ok(_) => panic!("deserialization should fail"),
266266+ Err(e) => assert_eq!(e.to_string(), "value is greater than 10"),
267267+ }
268268+ }
269269+270270+ {
271271+ #[derive(Deserialize, Debug)]
272272+ struct Foo {
273273+ bar: LimitedNonZeroU8<10>,
274274+ }
275275+276276+ match serde_json::from_str::<Foo>(r#"{"bar": 0}"#) {
277277+ Ok(_) => panic!("deserialization should fail"),
278278+ Err(e) => assert_eq!(e.to_string(), "value is zero at line 1 column 10"),
279279+ }
280280+ match serde_json::from_str::<Foo>(r#"{"bar": "0"}"#) {
281281+ Ok(_) => panic!("deserialization should fail"),
282282+ Err(e) => assert!(e.to_string().contains("invalid type: string")),
283283+ }
284284+ match serde_html_form::from_str::<Foo>(r#"bar=0"#) {
285285+ Ok(_) => panic!("deserialization should fail"),
286286+ Err(e) => assert_eq!(e.to_string(), "value is zero"),
287287+ }
288288+ match serde_html_form::from_str::<Foo>(r#"bar=10"#) {
289289+ Ok(foo) => assert_eq!(foo.bar, LimitedNonZeroU8::<10>::MAX),
290290+ Err(e) => panic!("failed to deserialize: {e}"),
291291+ }
292292+ match serde_html_form::from_str::<Foo>(r#"bar=11"#) {
293293+ Ok(_) => panic!("deserialization should fail"),
294294+ Err(e) => assert_eq!(e.to_string(), "value is greater than 10"),
295295+ }
296296+ }
297297+298298+ {
299299+ #[derive(Deserialize, Debug)]
300300+ struct Foo {
301301+ bar: BoundedU8<1, 10>,
302302+ }
303303+304304+ match serde_json::from_str::<Foo>(r#"{"bar": 0}"#) {
305305+ Ok(_) => panic!("deserialization should fail"),
306306+ Err(e) => assert_eq!(e.to_string(), "value is less than 1 at line 1 column 10"),
307307+ }
308308+ match serde_json::from_str::<Foo>(r#"{"bar": "0"}"#) {
309309+ Ok(_) => panic!("deserialization should fail"),
310310+ Err(e) => assert!(e.to_string().contains("invalid type: string")),
311311+ }
312312+ match serde_html_form::from_str::<Foo>(r#"bar=0"#) {
313313+ Ok(_) => panic!("deserialization should fail"),
314314+ Err(e) => assert_eq!(e.to_string(), "value is less than 1"),
315315+ }
316316+ match serde_html_form::from_str::<Foo>(r#"bar=10"#) {
317317+ Ok(foo) => assert_eq!(foo.bar, BoundedU8::<1, 10>::MAX),
318318+ Err(e) => panic!("failed to deserialize: {e}"),
319319+ }
320320+ match serde_html_form::from_str::<Foo>(r#"bar=11"#) {
321321+ Ok(_) => panic!("deserialization should fail"),
322322+ Err(e) => assert_eq!(e.to_string(), "value is greater than 10"),
323323+ }
324324+ }
325325+ }
326326+}
+202
crates/jacquard-common/src/types/tid.rs
···11+use std::fmt;
22+use std::sync::LazyLock;
33+use std::{ops::Deref, str::FromStr};
44+55+use compact_str::{CompactString, ToCompactString};
66+use serde::{Deserialize, Deserializer, Serialize, de::Error};
77+88+use crate::types::integer::LimitedU32;
99+use crate::{CowStr, IntoStatic};
1010+use regex::Regex;
1111+1212+fn s32_encode(mut i: u64) -> CowStr<'static> {
1313+ const S32_CHAR: &[u8] = b"234567abcdefghijklmnopqrstuvwxyz";
1414+1515+ let mut s = CompactString::with_capacity(13);
1616+ for _ in 0..13 {
1717+ let c = i & 0x1F;
1818+ s.push(S32_CHAR[c as usize] as char);
1919+2020+ i >>= 5;
2121+ }
2222+2323+ // Reverse the string to convert it to big-endian format.
2424+ CowStr::Owned(s.chars().rev().collect())
2525+}
2626+2727+static TID_REGEX: LazyLock<Regex> = LazyLock::new(|| {
2828+ Regex::new(r"^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$").unwrap()
2929+});
3030+3131+/// A [Timestamp Identifier].
3232+///
3333+/// [Timestamp Identifier]: https://atproto.com/specs/tid
3434+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Hash)]
3535+#[serde(transparent)]
3636+pub struct Tid<'t>(CowStr<'t>);
3737+3838+impl<'t> Tid<'t> {
3939+ /// Parses a `TID` from the given string.
4040+ pub fn new(tid: &'t str) -> Result<Self, &'static str> {
4141+ if tid.len() != 13 {
4242+ Err("TID must be 13 characters")
4343+ } else if !TID_REGEX.is_match(&tid) {
4444+ Err("Invalid TID")
4545+ } else {
4646+ Ok(Self(CowStr::Owned(tid.to_compact_string())))
4747+ }
4848+ }
4949+5050+ /// Fallible constructor from an existing CowStr, takes ownership
5151+ pub fn from_cowstr(tid: CowStr<'t>) -> Result<Tid<'t>, &'static str> {
5252+ if tid.len() != 13 {
5353+ Err("TID must be 13 characters")
5454+ } else if !TID_REGEX.is_match(&tid) {
5555+ Err("Invalid TID")
5656+ } else {
5757+ Ok(Self(tid.into_static()))
5858+ }
5959+ }
6060+6161+ /// Infallible constructor for when you *know* the string is a valid TID.
6262+ /// Will panic on invalid TID. If you're manually decoding atproto records
6363+ /// or API values you know are valid (rather than using serde), this is the one to use.
6464+ /// The From<String> and From<CowStr> impls use the same logic.
6565+ pub fn raw(tid: &'t str) -> Self {
6666+ if tid.len() != 13 {
6767+ panic!("TID must be 13 characters")
6868+ } else if !TID_REGEX.is_match(&tid) {
6969+ panic!("Invalid TID")
7070+ } else {
7171+ Self(CowStr::Borrowed(tid))
7272+ }
7373+ }
7474+7575+ /// Infallible constructor for when you *know* the string is a valid TID.
7676+ /// Marked unsafe because responsibility for upholding the invariant is on the developer.
7777+ pub unsafe fn unchecked(tid: &'t str) -> Self {
7878+ Self(CowStr::Borrowed(tid))
7979+ }
8080+8181+ /// Construct a new timestamp with the specified clock ID.
8282+ ///
8383+ /// If you have multiple clock sources, you can use `clkid` to distinguish between them
8484+ /// and hint to other implementations that the timestamp cannot be compared with other
8585+ /// timestamps from other sources.
8686+ /// If you are only using a single clock source, you can just specify `0` for `clkid`.
8787+ pub fn from_datetime(clkid: LimitedU32<1023>, time: chrono::DateTime<chrono::Utc>) -> Self {
8888+ let time = time.timestamp_micros() as u64;
8989+9090+ // The TID is laid out as follows:
9191+ // 0TTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTCCCCCCCCCC
9292+ let tid = (time << 10) & 0x7FFF_FFFF_FFFF_FC00 | (Into::<u32>::into(clkid) as u64 & 0x3FF);
9393+ Self(s32_encode(tid))
9494+ }
9595+9696+ /// Construct a new [Tid] that represents the current time.
9797+ ///
9898+ /// If you have multiple clock sources, you can use `clkid` to distinguish between them
9999+ /// and hint to other implementations that the timestamp cannot be compared with other
100100+ /// timestamps from other sources.
101101+ /// If you are only using a single clock source, you can just specify `0` for `clkid`.
102102+ ///
103103+ /// _Warning:_ It's possible that this function will return the same time more than once.
104104+ /// If it's important that these values be unique, you will want to repeatedly call this
105105+ /// function until a different time is returned.
106106+ pub fn now(clkid: LimitedU32<1023>) -> Self {
107107+ Self::from_datetime(clkid, chrono::Utc::now())
108108+ }
109109+110110+ /// Construct a new [Tid] that represents the current time with clkid 0.
111111+ ///
112112+ /// _Warning:_ It's possible that this function will return the same time more than once.
113113+ /// If it's important that these values be unique, you will want to repeatedly call this
114114+ /// function until a different time is returned.
115115+ pub fn now_0() -> Self {
116116+ Self::from_datetime(LimitedU32::from_str("0").unwrap(), chrono::Utc::now())
117117+ }
118118+119119+ /// Returns the TID as a string slice.
120120+ pub fn as_str(&self) -> &str {
121121+ {
122122+ let this = &self.0;
123123+ this
124124+ }
125125+ }
126126+}
127127+128128+impl FromStr for Tid<'_> {
129129+ type Err = &'static str;
130130+131131+ /// Has to take ownership due to the lifetime constraints of the FromStr trait.
132132+ /// Prefer `Did::new()` or `Did::raw` if you want to borrow.
133133+ fn from_str(s: &str) -> Result<Self, Self::Err> {
134134+ Self::from_cowstr(CowStr::Borrowed(s).into_static())
135135+ }
136136+}
137137+138138+impl<'de> Deserialize<'de> for Tid<'de> {
139139+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
140140+ where
141141+ D: Deserializer<'de>,
142142+ {
143143+ let value = Deserialize::deserialize(deserializer)?;
144144+ Self::new(value).map_err(D::Error::custom)
145145+ }
146146+}
147147+148148+impl fmt::Display for Tid<'_> {
149149+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150150+ f.write_str(&self.0)
151151+ }
152152+}
153153+154154+impl<'t> From<Tid<'t>> for String {
155155+ fn from(value: Tid<'t>) -> Self {
156156+ value.0.to_string()
157157+ }
158158+}
159159+160160+impl<'t> From<Tid<'t>> for CowStr<'t> {
161161+ fn from(value: Tid<'t>) -> Self {
162162+ value.0
163163+ }
164164+}
165165+166166+impl From<String> for Tid<'static> {
167167+ fn from(value: String) -> Self {
168168+ if value.len() != 13 {
169169+ panic!("TID must be 13 characters")
170170+ } else if !TID_REGEX.is_match(&value) {
171171+ panic!("Invalid TID")
172172+ } else {
173173+ Self(CowStr::Owned(value.to_compact_string()))
174174+ }
175175+ }
176176+}
177177+178178+impl<'t> From<CowStr<'t>> for Tid<'t> {
179179+ fn from(value: CowStr<'t>) -> Self {
180180+ if value.len() != 13 {
181181+ panic!("TID must be 13 characters")
182182+ } else if !TID_REGEX.is_match(&value) {
183183+ panic!("Invalid TID")
184184+ } else {
185185+ Self(value)
186186+ }
187187+ }
188188+}
189189+190190+impl AsRef<str> for Tid<'_> {
191191+ fn as_ref(&self) -> &str {
192192+ self.as_str()
193193+ }
194194+}
195195+196196+impl Deref for Tid<'_> {
197197+ type Target = str;
198198+199199+ fn deref(&self) -> &Self::Target {
200200+ self.as_str()
201201+ }
202202+}