unity-version 0.3.1

Unity Version parser
Documentation
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt, str::FromStr};
use thiserror::Error;

#[derive(PartialEq, Eq, Ord, Hash, Debug, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum ReleaseType {
    Alpha,
    Beta,
    Patch,
    Final,
}

impl PartialOrd for ReleaseType {
    fn partial_cmp(&self, other: &ReleaseType) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl fmt::Display for ReleaseType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            match *self {
                ReleaseType::Final => write!(f, "final"),
                ReleaseType::Patch => write!(f, "patch"),
                ReleaseType::Beta => write!(f, "beta"),
                ReleaseType::Alpha => write!(f, "alpha"),
            }
        } else {
            match *self {
                ReleaseType::Final => write!(f, "f"),
                ReleaseType::Patch => write!(f, "p"),
                ReleaseType::Beta => write!(f, "b"),
                ReleaseType::Alpha => write!(f, "a"),
            }
        }
    }
}

impl Default for ReleaseType {
    fn default() -> ReleaseType {
        ReleaseType::Final
    }
}

#[derive(Debug, Error, PartialEq)]
pub enum ReleaseTypeError {
    #[error("Unknown release type: {0}")]
    UnknownType(String),

    #[error("Invalid release type character: {0}")]
    InvalidCharacter(char),
}

impl FromStr for ReleaseType {
    type Err = ReleaseTypeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "a" => Ok(ReleaseType::Alpha),
            "b" => Ok(ReleaseType::Beta),
            "p" => Ok(ReleaseType::Patch),
            "f" => Ok(ReleaseType::Final),
            _ => Err(ReleaseTypeError::UnknownType(s.to_string())),
        }
    }
}

impl TryFrom<char> for ReleaseType {
    type Error = ReleaseTypeError;

    fn try_from(value: char) -> Result<Self, Self::Error> {
        match value {
            'f' => Ok(ReleaseType::Final),
            'b' => Ok(ReleaseType::Beta),
            'a' => Ok(ReleaseType::Alpha),
            'p' => Ok(ReleaseType::Patch),
            _ => Err(ReleaseTypeError::InvalidCharacter(value)),
        }
    }
}

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

    use proptest::prelude::*;

    #[test]
    fn should_return_final_when_given_f_as_input() {
        let result = ReleaseType::try_from('f');
        assert_eq!(result, Ok(ReleaseType::Final));
    }
    #[test]
    fn should_return_alpha_when_given_a_as_input() {
        let result = ReleaseType::try_from('a');
        assert_eq!(result, Ok(ReleaseType::Alpha));
    }
    #[test]
    fn should_return_beta_when_given_b_as_input() {
        let result = ReleaseType::try_from('b');
        assert_eq!(result, Ok(ReleaseType::Beta));
    }
    #[test]
    fn should_return_patch_when_given_p_as_input() {
        let result = ReleaseType::try_from('p');
        assert_eq!(result, Ok(ReleaseType::Patch));
    }
    #[test]
    fn should_return_error_when_given_empty_input() {
        let result = ReleaseType::try_from(' ');
        assert_eq!(result, Err(ReleaseTypeError::InvalidCharacter(' ')));
    }

    #[test]
    fn should_return_correct_to_string_value() {
        assert_eq!(ReleaseType::Final.to_string(), "f");
        assert_eq!(ReleaseType::Patch.to_string(), "p");
        assert_eq!(ReleaseType::Alpha.to_string(), "a");
        assert_eq!(ReleaseType::Beta.to_string(), "b");
    }
    #[test]
    fn should_format_using_correct_alternative_format() {
        assert_eq!(&format!("{:#}", ReleaseType::Final), "final");
        assert_eq!(&format!("{:#}", ReleaseType::Patch), "patch");
        assert_eq!(&format!("{:#}", ReleaseType::Beta), "beta");
        assert_eq!(&format!("{:#}", ReleaseType::Alpha), "alpha");
    }

    proptest! {
        #[test]
        fn from_str_does_not_crash(s in "\\PC*") {
            let _r = ReleaseType::from_str(&s);
        }

        #[test]
        fn from_str_supports_all_valid_cases(s in "(a|b|f|p)") {
            ReleaseType::from_str(&s).unwrap();
        }
    }
}