#![deny(missing_docs)]
use chrono::{DateTime, Utc};
use semver::Version as SemVer;
use serde::Deserialize;
use std::cmp::Ordering;
use std::fmt::{self, Display};
#[derive(Debug, Deserialize)]
pub struct Versions {
versions: Vec<Version>,
}
#[derive(Clone, Debug, Deserialize)]
#[non_exhaustive]
pub struct Version {
#[serde(rename = "num")]
version: SemVer,
pub yanked: bool,
pub created_at: DateTime<Utc>,
}
impl Versions {
pub fn max_version(&self) -> Option<&Version> {
self.versions
.iter()
.max_by(|v1, v2| v1.version.cmp(&v2.version))
}
pub fn max_unyanked_version(&self) -> Option<&Version> {
self.versions
.iter()
.filter(|v| !v.yanked)
.max_by(|v1, v2| v1.version.cmp(&v2.version))
}
pub fn max_yanked_version(&self) -> Option<&Version> {
self.versions
.iter()
.filter(|v| v.yanked)
.max_by(|v1, v2| v1.version.cmp(&v2.version))
}
pub fn max_minor_version(&self, major: u64) -> Option<&Version> {
self.versions
.iter()
.filter(|v| v.major() == major)
.max_by(|v1, v2| v1.version.cmp(&v2.version))
}
pub fn max_unyanked_minor_version(&self, major: u64) -> Option<&Version> {
self.versions
.iter()
.filter(|v| !v.yanked)
.filter(|v| v.major() == major)
.max_by(|v1, v2| v1.version.cmp(&v2.version))
}
pub fn max_yanked_minor_version(&self, major: u64) -> Option<&Version> {
self.versions
.iter()
.filter(|v| v.yanked)
.filter(|v| v.major() == major)
.max_by(|v1, v2| v1.version.cmp(&v2.version))
}
pub fn max_patch(&self, major: u64, minor: u64) -> Option<&Version> {
self.versions
.iter()
.filter(|v| v.major() == major)
.filter(|v| v.minor() == minor)
.max_by(|v1, v2| v1.version.cmp(&v2.version))
}
pub fn max_unyanked_patch(&self, major: u64, minor: u64) -> Option<&Version> {
self.versions
.iter()
.filter(|v| !v.yanked)
.filter(|v| v.major() == major)
.filter(|v| v.minor() == minor)
.max_by(|v1, v2| v1.version.cmp(&v2.version))
}
pub fn max_yanked_patch(&self, major: u64, minor: u64) -> Option<&Version> {
self.versions
.iter()
.filter(|v| v.yanked)
.filter(|v| v.major() == major)
.filter(|v| v.minor() == minor)
.max_by(|v1, v2| v1.version.cmp(&v2.version))
}
pub fn newest_version(&self) -> Option<&Version> {
self.versions
.iter()
.max_by(|v1, v2| v1.created_at.cmp(&v2.created_at))
}
pub fn newest_unyanked_version(&self) -> Option<&Version> {
self.versions
.iter()
.filter(|v| !v.yanked)
.max_by(|v1, v2| v1.created_at.cmp(&v2.created_at))
}
pub fn newest_yanked_version(&self) -> Option<&Version> {
self.versions
.iter()
.filter(|v| v.yanked)
.max_by(|v1, v2| v1.created_at.cmp(&v2.created_at))
}
pub fn versions(&self) -> &Vec<Version> {
&self.versions
}
pub fn versions_mut(&mut self) -> &mut Vec<Version> {
&mut self.versions
}
pub fn versions_owned(self) -> Vec<Version> {
self.versions
}
}
impl Version {
pub fn major(&self) -> u64 {
self.version.major
}
pub fn minor(&self) -> u64 {
self.version.minor
}
pub fn patch(&self) -> u64 {
self.version.patch
}
}
impl PartialEq<SemVer> for Version {
fn eq(&self, rhs: &SemVer) -> bool {
self.version.eq(&rhs)
}
}
impl PartialEq<str> for Version {
fn eq(&self, rhs: &str) -> bool {
match SemVer::parse(rhs) {
Ok(version) => self.eq(&version),
Err(_) => false,
}
}
}
impl PartialEq<&str> for Version {
fn eq(&self, rhs: &&str) -> bool {
self.eq(rhs.to_owned())
}
}
impl PartialOrd<SemVer> for Version {
fn partial_cmp(&self, rhs: &SemVer) -> Option<Ordering> {
self.version.partial_cmp(rhs)
}
}
impl PartialOrd<str> for Version {
fn partial_cmp(&self, rhs: &str) -> Option<Ordering> {
match SemVer::parse(rhs) {
Ok(version) => self.partial_cmp(&version),
Err(_) => None,
}
}
}
impl PartialOrd<&str> for Version {
fn partial_cmp(&self, rhs: &&str) -> Option<Ordering> {
self.partial_cmp(rhs.to_owned())
}
}
impl Display for Version {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.version)?;
if self.yanked {
write!(f, " (yanked)")
} else {
Ok(())
}
}
}
impl From<Version> for SemVer {
fn from(v: Version) -> SemVer {
v.version
}
}
fn build_url(crate_name: &str) -> String {
format!(
"https://crates.io/api/v1/crates/{crate_name}",
crate_name = crate_name,
)
}
#[cfg(feature = "async")]
pub mod r#async;
#[cfg(feature = "blocking")]
pub mod blocking;
#[macro_export]
macro_rules! crate_name {
() => {
env!("CARGO_PKG_NAME")
};
}
#[macro_export]
macro_rules! crate_version {
() => {
env!("CARGO_PKG_VERSION")
};
}
#[macro_export]
macro_rules! crate_major_version {
() => {
env!("CARGO_PKG_VERSION_MAJOR")
};
}
#[macro_export]
macro_rules! crate_minor_version {
() => {
env!("CARGO_PKG_VERSION_MINOR")
};
}
#[macro_export]
macro_rules! crate_patch {
() => {
env!("CARGO_PKG_VERSION_PATCH")
};
}
#[macro_export]
macro_rules! user_agent {
() => {
concat!($crate::crate_name!(), "/", $crate::crate_version!())
};
}
#[cfg(not(any(feature = "async", feature = "blocking")))]
compile_error!(
"\
`check-latest` is almost completely useless without either `async` or \
`blocking` enabled"
);
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDateTime;
use lazy_static::lazy_static;
lazy_static! {
static ref DONT_CARE_DATETIME: DateTime<Utc> = {
let naive = NaiveDateTime::from_timestamp(0, 0);
DateTime::from_utc(naive, Utc)
};
}
#[test]
fn is_greater_semver() {
let version = Version {
version: SemVer::parse("1.2.3").unwrap(),
yanked: false,
created_at: DONT_CARE_DATETIME.clone(),
};
let semver = SemVer::parse("1.2.0").unwrap();
assert!(version > semver);
}
#[test]
fn is_lesser_semver() {
let version = Version {
version: SemVer::parse("1.2.3").unwrap(),
yanked: false,
created_at: DONT_CARE_DATETIME.clone(),
};
let semver = SemVer::parse("1.3.0").unwrap();
assert!(version < semver);
}
#[test]
fn is_greater_str() {
let version = Version {
version: SemVer::parse("1.2.3").unwrap(),
yanked: false,
created_at: DONT_CARE_DATETIME.clone(),
};
assert!(version > "1.2.0");
}
#[test]
fn is_lesser_str() {
let version = Version {
version: SemVer::parse("1.2.3").unwrap(),
yanked: false,
created_at: DONT_CARE_DATETIME.clone(),
};
assert!(version < "1.3.0");
}
}