use super::{Dehydrate, Hydrate, StringError};
use std::{io::Cursor, str::FromStr};
#[derive(Debug)]
pub enum VersionError {
MismatchedId,
MismatchedVariant,
StringError(StringError),
}
impl From<StringError> for VersionError {
fn from(se: StringError) -> Self {
Self::StringError(se)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Version {
id: String,
variant: Option<String>,
}
impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.variant {
Some(variant) => write!(f, "{}.{}", self.id, variant),
None => write!(f, "{}", self.id),
}
}
}
impl FromStr for Version {
type Err = VersionError;
fn from_str(v: &str) -> Result<Version, VersionError> {
match v.split_once('.') {
Some((id, variant)) => Ok(Version {
id: id.to_owned(),
variant: Some(variant.to_owned()),
}),
None => Ok(Version {
id: v.to_owned(),
variant: None,
}),
}
}
}
impl Version {
pub fn try_negotiate(&self, other: &Version) -> Result<Version, VersionError> {
if self.id != other.id {
return Err(VersionError::MismatchedId);
}
if self.variant == other.variant || self.variant.is_none() {
return Ok(self.clone());
}
Err(VersionError::MismatchedVariant)
}
}
impl<T> Hydrate<T> for Version
where
T: AsRef<[u8]>,
{
type Error = VersionError;
fn hydrate(b: &mut Cursor<T>) -> Result<Self, Self::Error> {
String::hydrate(b)?.parse()
}
}
impl Dehydrate for Version {
type Error = VersionError;
fn dehydrate(&self, b: &mut Cursor<Vec<u8>>) -> Result<(), Self::Error> {
let s = self.to_string();
Ok(s.as_str().dehydrate(b)?)
}
}
#[cfg(test)]
mod tests {
use super::{Dehydrate, Hydrate, Version};
use crate::raw::test_round_trip;
use std::io::Cursor;
#[test]
fn parse() {
let v: Version = "9P2000".parse().unwrap();
assert_eq!(v.id, "9P2000");
assert_eq!(v.variant, None);
let v: Version = "9P2000.L".parse().unwrap();
assert_eq!(v.id, "9P2000");
assert_eq!(v.variant, Some("L".to_owned()));
}
#[test]
fn negotiate_matched() {
let v: Version = "9P2000".parse().unwrap();
let v1: Version = "9P2000.L".parse().unwrap();
assert_eq!(v.try_negotiate(&v).unwrap(), v, "9P2000 + 9P2000 = 9P2000");
assert_eq!(
v.try_negotiate(&v1).unwrap(),
v,
"9P2000 + 9P2000.L = 9P2000"
);
assert!(v1.try_negotiate(&v).is_err(), "9P2000.L + 9P2000 = Error");
let v2: Version = "9P2001.L".parse().unwrap();
assert!(v.try_negotiate(&v2).is_err());
assert!(v1.try_negotiate(&v2).is_err());
assert!(v2.try_negotiate(&v1).is_err());
assert!(v2.try_negotiate(&v).is_err());
}
test_round_trip!(
round_trip_version,
Version,
Version,
("9P2000".parse().unwrap(), "9P2000.L".parse().unwrap())
);
}