use std::borrow::Borrow;
use std::cmp::Ordering;
use std::fmt;
use std::ops::Deref;
use std::path::Path;
use std::str::FromStr;
use derive_more::*;
use get_size::GetSize;
use get_size_derive::*;
use regex::Regex;
use safecast::TryCastFrom;
#[cfg(feature = "stream")]
mod destream;
#[cfg(feature = "hash")]
mod hash;
#[cfg(feature = "serde")]
mod serde;
pub const RESERVED_CHARS: [&str; 21] = [
"/", "..", "~", "$", "`", "&", "|", "=", "^", "{", "}", "<", ">", "'", "\"", "?", ":", "@",
"#", "(", ")",
];
#[derive(Debug, Display, Error)]
#[display(fmt = "{}", msg)]
pub struct ParseError {
msg: String,
}
impl From<String> for ParseError {
fn from(msg: String) -> Self {
Self { msg }
}
}
impl From<&str> for ParseError {
fn from(msg: &str) -> Self {
Self {
msg: msg.to_string(),
}
}
}
pub struct Label {
id: &'static str,
}
impl Deref for Label {
type Target = str;
fn deref(&self) -> &Self::Target {
self.id
}
}
impl From<Label> for Id {
fn from(l: Label) -> Id {
Id {
inner: l.id.to_string(),
}
}
}
impl PartialEq<Id> for Label {
fn eq(&self, other: &Id) -> bool {
self.id == other.as_str()
}
}
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.id)
}
}
pub const fn label(id: &'static str) -> Label {
Label { id }
}
#[derive(Clone, Eq, Hash, GetSize, PartialEq, Ord, PartialOrd)]
pub struct Id {
inner: String,
}
impl Id {
#[inline]
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
pub fn starts_with(&self, prefix: &str) -> bool {
self.inner.starts_with(prefix)
}
}
impl AsRef<Path> for Id {
fn as_ref(&self) -> &Path {
self.inner.as_ref()
}
}
#[cfg(feature = "uuid")]
impl From<uuid::Uuid> for Id {
fn from(id: uuid::Uuid) -> Self {
Self {
inner: id.to_string(),
}
}
}
impl Borrow<str> for Id {
fn borrow(&self) -> &str {
&self.inner
}
}
impl Borrow<String> for Id {
fn borrow(&self) -> &String {
&self.inner
}
}
impl PartialEq<String> for Id {
fn eq(&self, other: &String) -> bool {
&self.inner == other
}
}
impl PartialEq<str> for Id {
fn eq(&self, other: &str) -> bool {
self.inner == other
}
}
impl<'a> PartialEq<&'a str> for Id {
fn eq(&self, other: &&'a str) -> bool {
self.inner == *other
}
}
impl PartialEq<Label> for Id {
fn eq(&self, other: &Label) -> bool {
self.inner == other.id
}
}
impl PartialEq<Id> for &str {
fn eq(&self, other: &Id) -> bool {
self == &other.inner
}
}
impl PartialOrd<String> for Id {
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
self.inner.partial_cmp(other)
}
}
impl PartialOrd<str> for Id {
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
self.inner.as_str().partial_cmp(other)
}
}
impl<'a> PartialOrd<&'a str> for Id {
fn partial_cmp(&self, other: &&'a str) -> Option<Ordering> {
self.inner.as_str().partial_cmp(*other)
}
}
impl From<usize> for Id {
fn from(u: usize) -> Id {
u.to_string().parse().expect("usize")
}
}
impl From<u64> for Id {
fn from(i: u64) -> Id {
i.to_string().parse().expect("64-bit unsigned int")
}
}
impl FromStr for Id {
type Err = ParseError;
fn from_str(id: &str) -> Result<Self, Self::Err> {
validate_id(id)?;
Ok(Id {
inner: id.to_string(),
})
}
}
impl TryCastFrom<String> for Id {
fn can_cast_from(id: &String) -> bool {
validate_id(id).is_ok()
}
fn opt_cast_from(id: String) -> Option<Id> {
id.parse().ok()
}
}
impl From<Id> for String {
fn from(id: Id) -> String {
id.inner
}
}
impl TryCastFrom<Id> for usize {
fn can_cast_from(id: &Id) -> bool {
id.as_str().parse::<usize>().is_ok()
}
fn opt_cast_from(id: Id) -> Option<usize> {
id.as_str().parse::<usize>().ok()
}
}
impl fmt::Debug for Id {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.inner)
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.inner)
}
}
fn validate_id(id: &str) -> Result<(), ParseError> {
if id.is_empty() {
return Err("cannot construct an empty Id".into());
}
let mut invalid_chars = id.chars().filter(|c| (*c as u8) < 32u8);
if let Some(invalid) = invalid_chars.next() {
return Err(format!(
"Id {} contains ASCII control characters {}",
id, invalid as u8,
)
.into());
}
for pattern in &RESERVED_CHARS {
if id.contains(pattern) {
return Err(format!("Id {} contains disallowed pattern {}", id, pattern).into());
}
}
if let Some(w) = Regex::new(r"\s").expect("whitespace regex").find(id) {
return Err(format!("Id {} is not allowed to contain whitespace {:?}", id, w).into());
}
Ok(())
}