use crate::{control::def_serde_traits_for, version::Version};
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq)]
pub struct SourceName {
pub name: String,
pub version: Option<Version>,
}
def_serde_traits_for!(SourceName);
impl std::fmt::Display for SourceName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match &self.version {
Some(version) => write!(f, "{} ({})", self.name, version),
None => write!(f, "{}", self.name),
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum SourceNameError {
Malformed,
BadVersion,
Empty,
VersionError(crate::version::Error),
}
crate::errors::error_enum!(SourceNameError);
impl FromStr for SourceName {
type Err = SourceNameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(SourceNameError::Empty);
}
if !s.contains(" ") {
return Ok(Self {
name: s.to_owned(),
version: None,
});
}
let [name, version] = s
.splitn(2, " ")
.collect::<Vec<_>>()
.try_into()
.map_err(|_| SourceNameError::Malformed)?;
if !version.starts_with("(") || !version.ends_with(")") {
return Err(SourceNameError::BadVersion);
}
Ok(Self {
name: name.to_owned(),
version: Some(
version[1..version.len() - 1]
.parse()
.map_err(SourceNameError::VersionError)?,
),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_normal() {
let source: SourceName = "foo".parse().unwrap();
assert_eq!("foo", source.name);
}
#[test]
fn check_with_version() {
let source: SourceName = "foo (1.0)".parse().unwrap();
assert_eq!("foo", source.name);
assert_eq!("1.0".parse::<Version>().unwrap(), source.version.unwrap());
}
macro_rules! check_fails {
($name:ident, $expr:expr ) => {
#[test]
fn $name() {
assert!($expr.parse::<SourceName>().is_err());
}
};
}
check_fails!(bad_empty, "");
check_fails!(bad_trailing, "foo ");
check_fails!(bad_empty_version, "foo ()");
check_fails!(bad_space, "foo ( )");
check_fails!(bad_unmatched_begin, "foo )");
check_fails!(bad_unmmatched_end, "foo (");
}