use std::fmt::Display;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MappingPath<'a> {
pub(crate) path: &'a str,
pub(crate) levels: Vec<&'a str>,
}
impl MappingPath<'_> {
#[must_use]
pub fn as_str(&self) -> &str {
self.path
}
#[must_use]
pub fn len(&self) -> usize {
self.levels.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.levels.is_empty()
}
}
impl Display for MappingPath<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.path)
}
}
impl<'a> TryFrom<&'a str> for MappingPath<'a> {
type Error = MappingPathError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
parse_mapping(value)
}
}
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)]
pub enum MappingPathError {
#[error("path missing prefix: {0}")]
Prefix(String),
#[error("path should have at least one level")]
Empty,
#[error("path has an empty level: {0}")]
EmptyLevel(String),
}
fn parse_mapping(input: &str) -> Result<MappingPath<'_>, MappingPathError> {
let path = input
.strip_prefix('/')
.ok_or_else(|| MappingPathError::Prefix(input.to_string()))?;
let levels: Vec<&str> = path
.split('/')
.map(|level| {
if level.is_empty() {
return Err(MappingPathError::EmptyLevel(input.to_string()));
}
Ok(level)
})
.collect::<Result<_, _>>()?;
if levels.is_empty() {
return Err(MappingPathError::Empty);
}
Ok(MappingPath {
path: input,
levels,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_endpoint() {
let path = MappingPath::try_from("/").unwrap_err();
assert_eq!(path, MappingPathError::EmptyLevel("/".into()));
}
#[test]
fn getters_success() {
let value = "/some/path";
let path = MappingPath::try_from(value).unwrap();
assert_eq!(path.as_str(), value);
assert_eq!(path.len(), 2);
assert!(!path.is_empty());
assert_eq!(path.to_string(), value);
}
#[test]
fn parse_mappings_success() {
let cases = [
"/foo/value",
"/bar/value",
"/value",
"/foo/bar/valu",
"/foo/value/ba",
];
for case in cases {
MappingPath::try_from(case).unwrap_or_else(|err| panic!("failed for {case}: {err}"));
}
}
#[test]
fn parse_mappings_error() {
let err = MappingPath::try_from("/").unwrap_err();
assert!(matches!(err, MappingPathError::EmptyLevel(_)), "{err:?}");
}
}