#[macro_export]
macro_rules! constrained_version {
($name:ident, $spec:expr, $default:expr) => {
#[doc = "Constrained PEP-440 version, defaulting to"]
#[doc = $default]
#[doc = ".\n\nInstantiate with [std::str::FromStr] or [TryFrom] [pep440_rs::Version]."]
#[doc = "Generated by the `constrained_version!` macro."]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name(pep440_rs::Version);
impl Default for $name {
fn default() -> Self {
static VERSION: std::sync::LazyLock<$name> = std::sync::LazyLock::new(|| {
let ver: pep440_rs::Version =
$default.parse().expect("Failed to parse version");
$name::try_from(ver).expect("Default version does not satisfy constraints")
});
VERSION.clone()
}
}
impl TryFrom<pep440_rs::Version> for $name {
type Error = $crate::Error;
fn try_from(version: pep440_rs::Version) -> Result<Self, Self::Error> {
static SPECIFIERS: std::sync::LazyLock<pep440_rs::VersionSpecifiers> =
std::sync::LazyLock::new(|| {
$spec.parse().expect("Failed to parse version constraints")
});
if SPECIFIERS.contains(&version) {
Ok(Self(version))
} else {
Err($crate::Error::VersionConstraint {
constraint: $spec,
version,
})
}
}
}
impl From<$name> for pep440_rs::Version {
fn from(cv: $name) -> Self {
cv.0
}
}
impl std::ops::Deref for $name {
type Target = pep440_rs::Version;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::str::FromStr for $name {
type Err = $crate::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let v: pep440_rs::Version = s.parse()?;
Self::try_from(v)
}
}
impl serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}
paste::paste! {
#[cfg(test)]
#[allow(non_snake_case)]
mod [<test_ $name>] {
use super::$name;
#[test]
fn test_valid_specifier() {
let _spec: pep440_rs::VersionSpecifiers =
$spec.parse().expect("Failed to parse version specifiers");
}
#[test]
fn test_default() {
let _default: $name = Default::default();
}
}
}
};
}