use crate::{types::Error, Result};
use serde_derive::Deserialize;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Deserialize, Hash, Eq)]
struct Segment(String);
impl Segment {
fn is_valid_char((idx, ch): (usize, char), len: usize) -> bool {
ch.is_alphanumeric() || (ch == '-' && idx != 0 && idx != len - 1)
}
fn create(s: &str) -> Result<Self> {
if !s.is_empty() {
if s.chars()
.enumerate()
.all(|v| Segment::is_valid_char(v, s.len()))
{
Ok(Segment(String::from(s)))
} else {
Err(Error::InvArgument(String::from(
"segment contains invalid character",
)))
}
} else {
Err(Error::InvArgument(String::from(
"contains zero-length segment",
)))
}
}
}
impl FromStr for Segment {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Segment::create(s)
}
}
impl fmt::Display for Segment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
#[derive(Debug, PartialEq, Clone, Deserialize, Hash, Eq)]
#[serde(try_from = "String")]
pub struct Path(Vec<Segment>);
impl Path {
pub fn create(s: &str) -> Result<Self> {
s.split(':')
.map(Segment::create)
.collect::<Result<Vec<Segment>>>()
.map(Path)
}
}
impl TryFrom<String> for Path {
type Error = Error;
fn try_from(s: String) -> Result<Self> {
Path::create(&s)
}
}
impl FromStr for Path {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Path::create(s)
}
}
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0[0])?;
for ii in &self.0[1..] {
write!(f, ":{}", &ii)?
}
Ok(())
}
}
#[derive(Debug, PartialEq, Clone, Deserialize, Hash, Eq)]
#[serde(try_from = "String")]
pub struct Base(Segment);
impl Base {
pub fn create(s: &str) -> Result<Self> {
Segment::create(s).map(Base)
}
}
impl TryFrom<String> for Base {
type Error = Error;
fn try_from(s: String) -> Result<Self> {
Base::create(&s)
}
}
impl FromStr for Base {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Base::create(s)
}
}
impl fmt::Display for Base {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &self.0)
}
}
#[derive(Debug, PartialEq, Hash, Eq, Clone, Deserialize)]
#[serde(try_from = "String")]
pub struct Name {
path: Path,
base: Base,
}
impl Name {
pub fn create(s: &str) -> Result<Name> {
match s
.split(':')
.map(Segment::create)
.collect::<Result<Vec<Segment>>>()
{
Ok(segments) if segments.len() < 2 => Err(Error::InvArgument(
String::from("device name requires a path and base name"),
)),
Ok(segments) => Ok(Name {
path: Path(segments[0..segments.len() - 1].to_vec()),
base: Base(segments[segments.len() - 1].clone()),
}),
Err(e) => Err(e),
}
}
pub fn build(path: Path, base: Base) -> Name {
Name { path, base }
}
pub fn get_path(&self) -> Path {
self.path.clone()
}
pub fn get_name(&self) -> Base {
self.base.clone()
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", &self.path, &self.base)
}
}
impl TryFrom<String> for Name {
type Error = Error;
fn try_from(s: String) -> Result<Self> {
Name::create(&s)
}
}
impl FromStr for Name {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Name::create(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_segment() {
assert!("".parse::<Segment>().is_err());
assert!(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
.parse::<Segment>()
.is_ok()
);
assert!("a-b".parse::<Segment>().is_ok());
assert!("a:b".parse::<Segment>().is_err());
assert!("-a".parse::<Segment>().is_err());
assert!("a-".parse::<Segment>().is_err());
assert!(" ".parse::<Segment>().is_err());
assert_eq!(format!("{}", "a-b".parse::<Segment>().unwrap()), "a-b");
assert!("٣".parse::<Segment>().is_ok());
assert!("温度".parse::<Segment>().is_ok());
assert!("🤖".parse::<Segment>().is_err());
}
#[test]
fn test_base() {
assert_eq!(format!("{}", "a-b".parse::<Base>().unwrap()), "a-b");
assert!("a:b".parse::<Base>().is_err());
}
#[test]
fn test_path() {
assert!("".parse::<Path>().is_err());
assert!("basement:🤖".parse::<Path>().is_err());
assert_eq!(format!("{}", "a-b".parse::<Path>().unwrap()), "a-b");
assert_eq!(format!("{}", "a:b".parse::<Path>().unwrap()), "a:b");
assert_eq!(format!("{}", "a:b:c".parse::<Path>().unwrap()), "a:b:c");
assert_eq!(
format!("{}", "家:温度".parse::<Path>().unwrap()),
"家:温度"
);
}
#[test]
fn test_device_name() {
assert!("".parse::<Name>().is_err());
assert!(":".parse::<Name>().is_err());
assert!("a".parse::<Name>().is_err());
assert!(":a".parse::<Name>().is_err());
assert!("a:".parse::<Name>().is_err());
assert!("a::a".parse::<Name>().is_err());
assert!("p:a.".parse::<Name>().is_err());
assert!("p:a.a".parse::<Name>().is_err());
assert!("p.a:a".parse::<Name>().is_err());
assert!("p:a-".parse::<Name>().is_err());
assert!("p:-a".parse::<Name>().is_err());
assert!("p-:a".parse::<Name>().is_err());
assert!("-p:a".parse::<Name>().is_err());
assert_eq!(
"p:abc".parse::<Name>().unwrap(),
Name {
path: Path::create("p").unwrap(),
base: Base::create("abc").unwrap(),
}
);
assert_eq!(
"p:abc1".parse::<Name>().unwrap(),
Name {
path: Path::create("p").unwrap(),
base: Base::create("abc1").unwrap(),
}
);
assert_eq!(
"p:abc-1".parse::<Name>().unwrap(),
Name {
path: Path::create("p").unwrap(),
base: Base::create("abc-1").unwrap(),
}
);
assert_eq!(
"p-1:p-2:abc".parse::<Name>().unwrap(),
Name {
path: Path::create("p-1:p-2").unwrap(),
base: Base::create("abc").unwrap(),
}
);
let dn = "p-1:p-2:abc".parse::<Name>().unwrap();
assert_eq!(dn.get_path(), Path::create("p-1:p-2").unwrap());
assert_eq!(dn.get_name(), Base::create("abc").unwrap());
assert_eq!(format!("{}", dn), "p-1:p-2:abc");
}
}