use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PhpVersion {
major: u8,
minor: u8,
}
impl PhpVersion {
pub const LATEST: PhpVersion = PhpVersion::new(8, 5);
pub const fn new(major: u8, minor: u8) -> Self {
Self { major, minor }
}
pub const fn major(self) -> u8 {
self.major
}
pub const fn minor(self) -> u8 {
self.minor
}
pub fn includes_symbol(self, since: Option<&str>, removed: Option<&str>) -> bool {
let parse_php = |s: &str| -> Option<PhpVersion> {
let v = s.parse::<PhpVersion>().ok()?;
if v.major() >= 4 && v.major() <= PhpVersion::LATEST.major() {
Some(v)
} else {
None
}
};
if let Some(s) = since.and_then(parse_php) {
if self < s {
return false;
}
}
if let Some(r) = removed.and_then(parse_php) {
if self >= r {
return false;
}
}
true
}
}
impl Default for PhpVersion {
fn default() -> Self {
Self::LATEST
}
}
impl fmt::Display for PhpVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid PHP version `{0}`: expected `MAJOR.MINOR` (e.g. `8.2`)")]
pub struct ParsePhpVersionError(pub String);
impl FromStr for PhpVersion {
type Err = ParsePhpVersionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.trim().split('.');
let major = parts
.next()
.and_then(|p| p.parse::<u8>().ok())
.ok_or_else(|| ParsePhpVersionError(s.to_string()))?;
let minor = parts.next().and_then(|p| p.parse::<u8>().ok()).unwrap_or(0);
Ok(Self::new(major, minor))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_major_minor() {
assert_eq!("8.2".parse::<PhpVersion>().unwrap(), PhpVersion::new(8, 2));
}
#[test]
fn parses_major_minor_patch() {
assert_eq!(
"8.3.7".parse::<PhpVersion>().unwrap(),
PhpVersion::new(8, 3)
);
}
#[test]
fn parses_major_only() {
assert_eq!("7".parse::<PhpVersion>().unwrap(), PhpVersion::new(7, 0));
}
#[test]
fn rejects_garbage() {
assert!("x.y".parse::<PhpVersion>().is_err());
assert!("".parse::<PhpVersion>().is_err());
}
#[test]
fn ordered_by_major_then_minor() {
assert!(PhpVersion::new(8, 1) < PhpVersion::new(8, 2));
assert!(PhpVersion::new(7, 4) < PhpVersion::new(8, 0));
}
#[test]
fn displays_as_major_dot_minor() {
assert_eq!(PhpVersion::new(8, 3).to_string(), "8.3");
}
#[test]
fn includes_symbol_respects_since() {
assert!(!PhpVersion::new(7, 4).includes_symbol(Some("8.0"), None));
assert!(PhpVersion::new(8, 0).includes_symbol(Some("8.0"), None));
assert!(PhpVersion::new(8, 5).includes_symbol(Some("8.0"), None));
}
#[test]
fn includes_symbol_respects_removed() {
assert!(PhpVersion::new(7, 4).includes_symbol(None, Some("8.0")));
assert!(!PhpVersion::new(8, 0).includes_symbol(None, Some("8.0")));
assert!(!PhpVersion::new(8, 5).includes_symbol(None, Some("8.0")));
}
#[test]
fn includes_symbol_ignores_library_versions() {
assert!(PhpVersion::new(8, 5).includes_symbol(Some("9.12"), None));
assert!(PhpVersion::new(8, 5).includes_symbol(Some("1.17"), None));
assert!(PhpVersion::new(8, 5).includes_symbol(Some("12.0"), None));
}
#[test]
fn includes_symbol_ignores_garbage() {
assert!(PhpVersion::new(8, 5).includes_symbol(Some("PECL"), None));
assert!(PhpVersion::new(8, 5).includes_symbol(Some(""), None));
}
}