chadtmiller.com/atproto
atproto's primitives in Rust, small and free of I/O
atproto 0.1.1 Latest 1 package
0
0
//! What atproto's identifiers may look like, as its specs give their
//! syntax: DIDs, handles, NSIDs, record keys and TIDs. Each says only
//! whether a string is well formed, not whether it names anything.
/// A DID: `did:`, a lower-case method, and an identifier of letters,
/// digits and `._:%-` not ending in `:` or `%`, 2 KiB at most.
pub fn is_did(s: &str) -> bool {
let Some(rest) = s.strip_prefix("did:") else {
return false;
};
let Some((method, id)) = rest.split_once(':') else {
return false;
};
s.len() <= 2048
&& !method.is_empty()
&& method.bytes().all(|c| c.is_ascii_lowercase())
&& !id.is_empty()
&& id
.bytes()
.all(|c| c.is_ascii_alphanumeric() || b"._:%-".contains(&c))
&& !id.ends_with([':', '%'])
}
/// A handle: two or more labels of letters, digits and inner hyphens, each
/// 63 characters at most, the last not starting with a digit, 253 in all.
pub fn is_handle(s: &str) -> bool {
let labels: Vec<&str> = s.split('.').collect();
s.len() <= 253
&& labels.len() >= 2
&& labels.iter().all(|l| is_label(l))
&& !labels[labels.len() - 1].starts_with(|c: char| c.is_ascii_digit())
}
fn is_label(l: &str) -> bool {
(1..=63).contains(&l.len())
&& l.bytes().all(|c| c.is_ascii_alphanumeric() || c == b'-')
&& !l.starts_with('-')
&& !l.ends_with('-')
}
/// An NSID: a reversed domain of two or more labels, its first not
/// starting with a digit, then a name of letters and digits not starting
/// with one, 317 characters in all. The interop vectors hold a domain
/// longer than a hostname's 253 as valid, so this does too.
pub fn is_nsid(s: &str) -> bool {
let Some((domain, name)) = s.rsplit_once('.') else {
return false;
};
let domain: Vec<&str> = domain.split('.').collect();
s.len() <= 317
&& domain.len() >= 2
&& domain.iter().all(|l| is_label(l))
&& !domain[0].starts_with(|c: char| c.is_ascii_digit())
&& (1..=63).contains(&name.len())
&& name.bytes().all(|c| c.is_ascii_alphanumeric())
&& !name.starts_with(|c: char| c.is_ascii_digit())
}
/// A record key: 1 to 512 of letters, digits and `._:~-`, but not `.` or `..`.
pub fn is_record_key(s: &str) -> bool {
(1..=512).contains(&s.len())
&& s.bytes()
.all(|c| c.is_ascii_alphanumeric() || b"._:~-".contains(&c))
&& s != "."
&& s != ".."
}
/// A TID: 13 characters of sortable base32, the first holding no top bit.
pub fn is_tid(s: &str) -> bool {
let alphabet = crate::base32::SORTABLE;
s.len() == 13
&& alphabet[..16].contains(&s.as_bytes()[0])
&& s.bytes().all(|c| alphabet.contains(&c))
}