trey 0.2.0

Writing V3000 CTfiles.
Documentation
use std::hash::{Hash, Hasher};
use std::{convert, fmt};

use super::Error;

#[derive(Debug, PartialEq, Eq, Clone, PartialOrd, Ord)]
pub struct Index(String);

impl Index {
    pub fn new(id: usize) -> Self {
        id.to_string().try_into().unwrap()
    }
}

impl fmt::Display for Index {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl Default for Index {
    fn default() -> Self {
        Self("1".into())
    }
}

impl Hash for Index {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

impl convert::TryFrom<String> for Index {
    type Error = Error;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        if value.is_empty() {
            return Err(Error::IdFormat);
        }

        for (i, character) in value.chars().enumerate() {
            match character {
                '0' => {
                    if i == 0 {
                        return Err(Error::IdFormat);
                    }
                }
                '1'..='9' => (),
                _ => return Err(Error::IdFormat),
            }
        }

        Ok(Self(value))
    }
}

impl convert::TryFrom<&str> for Index {
    type Error = Error;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Self::try_from(value.to_owned())
    }
}

impl convert::TryFrom<usize> for Index {
    type Error = Error;

    fn try_from(value: usize) -> Result<Self, Self::Error> {
        Self::try_from(value.to_string())
    }
}

impl convert::TryFrom<u32> for Index {
    type Error = Error;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        Self::try_from(value.to_string())
    }
}

impl From<&Index> for u32 {
    fn from(value: &Index) -> Self {
        value.0.parse().unwrap()
    }
}

#[cfg(test)]
mod try_from {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn empty() {
        assert_eq!(Index::try_from(""), Err(Error::IdFormat))
    }

    #[test]
    fn leading_zero_string() {
        assert_eq!(Index::try_from("01"), Err(Error::IdFormat))
    }

    #[test]
    fn non_numeric_string() {
        assert_eq!(Index::try_from("foobar"), Err(Error::IdFormat))
    }

    #[test]
    fn valid_string() {
        assert_eq!(Index::try_from("10").unwrap().to_string(), "10")
    }
}

#[cfg(test)]
mod equals {
    use super::*;

    #[test]
    fn same() {
        let first = Index::try_from("1").unwrap();
        let second = Index::try_from("1").unwrap();

        assert!(first == second)
    }

    #[test]
    fn different() {
        let first = Index::try_from("1").unwrap();
        let second = Index::try_from("2").unwrap();

        assert!(first != second)
    }
}

#[cfg(test)]
mod hash_map {
    use std::{collections::HashMap, convert::TryFrom};

    use super::*;

    #[test]
    fn same() {
        let mut ids = HashMap::new();

        ids.insert(Index::try_from("1").unwrap(), 1);

        assert!(ids.contains_key(&Index::try_from("1").unwrap()))
    }

    #[test]
    fn different() {
        let mut ids = HashMap::new();

        ids.insert(Index::try_from("1").unwrap(), 1);

        assert!(!ids.contains_key(&Index::try_from("2").unwrap()))
    }
}