use std::cmp::{Eq, PartialEq};
use std::convert::{From, TryFrom};
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str::Split;
use thiserror::Error;
enum Input {
AlphanumericAndUnderscore,
Slash,
}
impl TryFrom<u8> for Input {
type Error = ObjectPathError;
fn try_from(c: u8) -> Result<Self, Self::Error> {
if c.is_ascii_alphanumeric() || c == b'_' {
Ok(Input::AlphanumericAndUnderscore)
} else if c == b'/' {
Ok(Input::Slash)
} else {
Err(ObjectPathError::InvalidChar(c))
}
}
}
enum State {
Start,
Root,
ElementBegin,
Element,
}
impl State {
fn consume(self, i: Input) -> Result<State, ObjectPathError> {
match self {
State::Start => match i {
Input::AlphanumericAndUnderscore => {
Err(ObjectPathError::BeginAlphanumericAndUnderscoreAndHyphen)
}
Input::Slash => Ok(State::Root),
},
State::Root => match i {
Input::AlphanumericAndUnderscore => Ok(State::Element),
Input::Slash => Err(ObjectPathError::ElementEmtpy),
},
State::ElementBegin => match i {
Input::AlphanumericAndUnderscore => Ok(State::Element),
Input::Slash => Err(ObjectPathError::ElementEmtpy),
},
State::Element => match i {
Input::AlphanumericAndUnderscore => Ok(State::Element),
Input::Slash => Ok(State::ElementBegin),
},
}
}
}
fn check(error: &[u8]) -> Result<(), ObjectPathError> {
let mut state = State::Start;
for c in error {
let i = Input::try_from(*c)?;
state = state.consume(i)?;
}
match state {
State::Start => Err(ObjectPathError::Empty),
State::Root => Ok(()),
State::ElementBegin => Err(ObjectPathError::EndSlash),
State::Element => Ok(()),
}
}
#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
pub struct ObjectPath(String);
#[derive(Debug, PartialEq, Eq, Error)]
pub enum ObjectPathError {
#[error("ObjectPath must not begin with an alphanumeric or with a '_' or with a '-'")]
BeginAlphanumericAndUnderscoreAndHyphen,
#[error("ObjectPath must not end with '.'")]
EndSlash,
#[error("ObjectPath must not be empty")]
Empty,
#[error("ObjectPath element must not be empty")]
ElementEmtpy,
#[error("ObjectPath must only contain '[A-Z][a-z][0-9]_/': {0}")]
InvalidChar(u8),
}
impl From<ObjectPath> for String {
fn from(object_path: ObjectPath) -> Self {
object_path.0
}
}
impl TryFrom<String> for ObjectPath {
type Error = ObjectPathError;
fn try_from(object_path: String) -> Result<Self, Self::Error> {
check(object_path.as_bytes())?;
Ok(ObjectPath(object_path))
}
}
impl TryFrom<&str> for ObjectPath {
type Error = ObjectPathError;
fn try_from(object_path: &str) -> Result<Self, Self::Error> {
check(object_path.as_bytes())?;
Ok(ObjectPath(object_path.to_owned()))
}
}
impl TryFrom<&[u8]> for ObjectPath {
type Error = ObjectPathError;
fn try_from(object_path: &[u8]) -> Result<Self, Self::Error> {
check(object_path)?;
let object_path = object_path.to_vec();
unsafe { Ok(ObjectPath(String::from_utf8_unchecked(object_path))) }
}
}
impl Display for ObjectPath {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.0)
}
}
impl AsRef<str> for ObjectPath {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Default for ObjectPath {
fn default() -> Self {
ObjectPath("/".to_string())
}
}
impl PartialEq<str> for ObjectPath {
fn eq(&self, other: &str) -> bool {
self.as_ref() == other
}
}
impl ObjectPath {
pub fn append(&mut self, element: &str) -> bool {
for c in element.as_bytes() {
if !c.is_ascii_alphanumeric() && *c != b'_' {
return false;
}
}
if self.0 != "/" {
self.0 += "/";
}
self.0 += element;
true
}
pub fn starts_with(&self, base: &ObjectPath) -> bool {
if let Some(mut iter) = self.strip_prefix_elements(base) {
iter.next().is_some()
} else {
false
}
}
pub fn strip_prefix_elements<'a, 'b>(
&'a self,
base: &'b ObjectPath,
) -> Option<Split<'a, char>> {
let mut self_iter = self.0.split('/');
if self != "/" && base == "/" {
self_iter.next()?;
return Some(self_iter);
}
let mut base_iter = base.0.split('/');
loop {
let self_iter_prev = self_iter.clone();
match (self_iter.next(), base_iter.next()) {
(Some(ref x), Some(ref y)) => {
if x != y {
return None;
}
}
(Some(_), None) => return Some(self_iter_prev),
(None, None) => return None,
(None, Some(_)) => return None,
}
}
}
}