mod address;
mod agent;
mod inbox;
mod user;
pub use address::Address;
pub use agent::AgentId;
pub use inbox::InboxId;
pub use user::UserId;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum IdentityError {
#[error("invalid agent id")]
InvalidAgent,
#[error("invalid user id")]
InvalidUser,
#[error("invalid inbox")]
InvalidInbox,
#[error("invalid address")]
InvalidAddress,
#[error("invalid identity string")]
InvalidIdentityString,
#[error("invalid fqdn string")]
InvalidFqdnString,
}
pub const ALLOWED_SPECIAL_CHARS: &str = ".!$%&'*+-/=?^_`{}~";
pub fn is_valid_identity_string(input: &str) -> bool {
input
.chars()
.all(|c| c.is_ascii_alphanumeric() || ALLOWED_SPECIAL_CHARS.contains(c))
}
pub fn canonical_identity_string(input: &str) -> String {
input.to_lowercase()
}
pub fn is_valid_dns_label(label: &str) -> bool {
let len = label.len();
if len == 0 || len > 63 {
return false;
}
let bytes = label.as_bytes();
if !bytes[0].is_ascii_alphanumeric() || !bytes[len - 1].is_ascii_alphanumeric() {
return false;
}
bytes
.iter()
.all(|b| b.is_ascii_alphanumeric() || *b == b'-')
}
pub fn is_valid_fqdn(input: &str) -> bool {
if input.len() > 253 {
return false;
}
if input.ends_with('.') {
return false;
}
let labels: Vec<&str> = input.split('.').collect();
if labels.len() < 2 {
return false;
}
labels.iter().all(|label| is_valid_dns_label(label))
}
#[cfg(test)]
mod tests {
use super::*;
mod identity_string {
use super::*;
#[test]
fn correct_allowed_chars() {
let test_str = "abcXYZ0123456789.!$%&'*+-/=?^_`{}~";
assert!(is_valid_identity_string(test_str));
}
#[test]
fn rejects_common_invalid_chars() {
assert!(!is_valid_identity_string("#"));
assert!(!is_valid_identity_string("@"));
assert!(!is_valid_identity_string("|"));
assert!(!is_valid_identity_string(" "));
}
#[test]
fn canonicalizes_properly() {
let input = "AbC.!$%&'*+-/=?^_`{}~XyZ0123456789";
let expected = "abc.!$%&'*+-/=?^_`{}~xyz0123456789";
assert_eq!(canonical_identity_string(input), expected);
}
}
mod dns_label {
use super::*;
#[test]
fn valid_labels() {
assert!(is_valid_dns_label("example"));
assert!(is_valid_dns_label("ex-ample"));
assert!(is_valid_dns_label("e"));
assert!(is_valid_dns_label("a".repeat(63).as_str()));
}
#[test]
fn invalid_labels() {
assert!(!is_valid_dns_label(""));
assert!(!is_valid_dns_label("a".repeat(64).as_str()));
assert!(!is_valid_dns_label("-example"));
assert!(!is_valid_dns_label("example-"));
assert!(!is_valid_dns_label("ex_ample"));
assert!(!is_valid_dns_label("ex ample"));
}
}
mod fqdn {
use super::*;
#[test]
fn valid_fqdns() {
assert!(is_valid_fqdn("example.org"));
assert!(is_valid_fqdn("sub.example.org"));
assert!(is_valid_fqdn(
"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z"
));
let labels = [
"a".repeat(63),
"b".repeat(63),
"c".repeat(63),
"d".repeat(61),
];
let fqdn = labels.join(".");
assert_eq!(fqdn.len(), 253);
assert!(is_valid_fqdn(&fqdn));
}
#[test]
fn invalid_fqdns() {
assert!(!is_valid_fqdn(""));
assert!(!is_valid_fqdn("example."));
assert!(!is_valid_fqdn("ex ample.org"));
assert!(!is_valid_fqdn("example_org"));
assert!(!is_valid_fqdn(&format!("{}.org", "a".repeat(247))));
assert!(!is_valid_fqdn("a..b.org"));
assert!(!is_valid_fqdn("a.-b.org"));
}
}
}