use regex::Regex;
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::ops::Deref;
use thiserror::Error;
lazy_static::lazy_static! {
pub(crate) static ref PN_PREFIX: Regex = Regex::new(r"(?x)^
# PN_CHAR_BASE
[A-Za-z\u{00C0}-\u{00D6}\u{00D8}-\u{00F6}\u{00F8}-\u{02FF}\u{0370}-\u{037D}\u{037F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}]
(
# [ PN_CHARS | '.' ]*
[A-Za-z\u{00C0}-\u{00D6}\u{00D8}-\u{00F6}\u{00F8}-\u{02FF}\u{0370}-\u{037D}\u{037F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}_0-9\u{00B7}\u{0300}-\u{036F}\u{203F}-\u{2040}.-]*
# PN_CHARS
[A-Za-z\u{00C0}-\u{00D6}\u{00D8}-\u{00F6}\u{00F8}-\u{02FF}\u{0370}-\u{037D}\u{037F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}_0-9\u{00B7}\u{0300}-\u{036F}\u{203F}-\u{2040}-]
)?
$").unwrap();
}
pub fn is_valid_prefix(txt: &str) -> bool {
txt.is_empty() || PN_PREFIX.is_match(txt)
}
pub trait IsPrefix: Borrow<str> {}
pub trait AsPrefix {
fn as_prefix(&self) -> Prefix;
}
impl<T: IsPrefix> AsPrefix for T {
fn as_prefix(&self) -> Prefix {
Prefix::new_unchecked(self.borrow())
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Prefix<'a>(&'a str);
impl<'a> Prefix<'a> {
pub fn new_unchecked(prefix: &'a str) -> Self {
Prefix(prefix)
}
pub fn new(prefix: &'a str) -> Result<Self, InvalidPrefix> {
if is_valid_prefix(prefix) {
Ok(Prefix(prefix))
} else {
Err(InvalidPrefix(prefix.to_string()))
}
}
pub fn boxed(self) -> PrefixBox {
PrefixBox::new_unchecked(Box::from(self.0))
}
}
impl<'a> IsPrefix for Prefix<'a> {}
impl<'a> AsRef<str> for Prefix<'a> {
fn as_ref(&self) -> &str {
self.0
}
}
impl<'a> Borrow<str> for Prefix<'a> {
fn borrow(&self) -> &str {
self.0
}
}
impl<'a> Deref for Prefix<'a> {
type Target = str;
fn deref(&self) -> &str {
self.0
}
}
impl<'a> PartialEq<str> for Prefix<'a> {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl<'a> PartialEq<Prefix<'a>> for str {
fn eq(&self, other: &Prefix<'a>) -> bool {
self == other.0
}
}
impl<'a> PartialOrd<str> for Prefix<'a> {
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl<'a> PartialOrd<Prefix<'a>> for str {
fn partial_cmp(&self, other: &Prefix<'a>) -> Option<Ordering> {
self.partial_cmp(other.0)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PrefixBox(Box<str>);
impl PrefixBox {
pub fn new_unchecked(prefix: Box<str>) -> Self {
PrefixBox(prefix)
}
pub fn new(prefix: Box<str>) -> Result<Self, InvalidPrefix> {
if is_valid_prefix(&prefix) {
Ok(PrefixBox(prefix))
} else {
Err(InvalidPrefix(prefix.to_string()))
}
}
}
impl IsPrefix for PrefixBox {}
impl Borrow<str> for PrefixBox {
fn borrow(&self) -> &str {
&self.0
}
}
impl AsRef<str> for PrefixBox {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Deref for PrefixBox {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl PartialEq<str> for PrefixBox {
fn eq(&self, other: &str) -> bool {
&*self.0 == other
}
}
impl PartialEq<PrefixBox> for str {
fn eq(&self, other: &PrefixBox) -> bool {
self == &*other.0
}
}
impl PartialOrd<str> for PrefixBox {
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
(*self.0).partial_cmp(other)
}
}
impl PartialOrd<PrefixBox> for str {
fn partial_cmp(&self, other: &PrefixBox) -> Option<Ordering> {
self.partial_cmp(&*other.0)
}
}
#[derive(Debug, Error)]
#[error("The given prefix '{0}' does not match PN_PREFIX?")]
pub struct InvalidPrefix(pub String);
#[cfg(test)]
#[allow(clippy::unused_unit)] mod test {
use super::*;
use test_case::test_case;
#[test_case(""; "empty")]
#[test_case("a")]
#[test_case("foo")]
#[test_case("é.hê"; "with dot and accents")]
fn valid_prefix(p: &str) {
assert!(is_valid_prefix(p));
assert!(Prefix::new(p).is_ok());
assert!(PrefixBox::new(p.into()).is_ok());
}
#[test_case(" "; "space")]
#[test_case("1a")]
#[test_case("a."; "ending with dot")]
fn invalid_prefix(p: &str) {
assert!(!is_valid_prefix(p));
assert!(Prefix::new(p).is_err());
assert!(PrefixBox::new(p.into()).is_err());
}
}