use semver::Version;
use serde::Serialize;
pub const MAX_RETRY_ATTEMPTS: u32 = 3;
pub const RETRY_DELAY_MS: u64 = 2000;
pub const VERSION_UPDATE_DELAY_MS: u64 = 1000;
pub const PROGRESS_TICK_MS: u64 = 100;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PackageSource {
Crates,
Git {
url: String,
rev: Option<String>,
},
Path {
dir: String,
},
Unknown(String),
}
impl PackageSource {
pub fn is_crates(&self) -> bool {
matches!(self, PackageSource::Crates)
}
pub fn marker(&self) -> &'static str {
match self {
PackageSource::Crates => "",
PackageSource::Git { .. } => "[git]",
PackageSource::Path { .. } => "[path]",
PackageSource::Unknown(_) => "[unknown source]",
}
}
pub fn skip_reason_code(&self) -> &'static str {
match self {
PackageSource::Crates => "crates_source",
PackageSource::Git { .. } => "git_source",
PackageSource::Path { .. } => "path_source",
PackageSource::Unknown(_) => "unknown_source",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct InstallOpts {
pub no_default_features: bool,
pub all_features: bool,
pub features: Vec<String>,
}
impl InstallOpts {
pub fn is_default(&self) -> bool {
!self.no_default_features && !self.all_features && self.features.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CheckErrorKind {
NotFound,
Unavailable,
}
impl CheckErrorKind {
pub fn kind_str(&self) -> &'static str {
match self {
CheckErrorKind::NotFound => "not_found",
CheckErrorKind::Unavailable => "unavailable",
}
}
}
#[derive(Debug, Clone)]
pub struct CheckError {
pub kind: CheckErrorKind,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinstallKind {
Prebuilt,
SourceBuild,
Unknown,
}
impl BinstallKind {
pub fn kind_str(self) -> &'static str {
match self {
BinstallKind::Prebuilt => "prebuilt",
BinstallKind::SourceBuild => "source_build",
BinstallKind::Unknown => "unknown",
}
}
pub fn marker(self) -> &'static str {
match self {
BinstallKind::Prebuilt => "[binstall: prebuilt]",
BinstallKind::SourceBuild => "[binstall: source build]",
BinstallKind::Unknown => "[binstall: unknown]",
}
}
}
#[derive(Debug)]
pub struct PackageInfo {
pub name: String,
pub current_version: Option<String>,
pub latest_version: Option<String>,
pub source: PackageSource,
pub install_opts: Option<InstallOpts>,
pub check_error: Option<CheckError>,
pub binstall_kind: Option<BinstallKind>,
}
#[derive(Debug, Clone)]
pub struct UpdateResult {
pub package_name: String,
pub old_version: Option<String>,
pub new_version: Option<String>,
pub success: bool,
}
impl PackageInfo {
#[allow(dead_code)]
pub fn new(name: String, current_version: Option<String>) -> Self {
Self::with_source(name, current_version, PackageSource::Crates)
}
pub fn with_source(
name: String,
current_version: Option<String>,
source: PackageSource,
) -> Self {
Self {
name,
current_version,
latest_version: None,
source,
install_opts: None,
check_error: None,
binstall_kind: None,
}
}
pub fn has_update(&self) -> bool {
match (&self.current_version, &self.latest_version) {
(Some(current), Some(latest)) => {
match (Version::parse(current), Version::parse(latest)) {
(Ok(c), Ok(l)) => l > c,
_ => current != latest,
}
}
_ => false,
}
}
pub fn is_prerelease(&self) -> bool {
self.latest_version
.as_ref()
.and_then(|v| Version::parse(v).ok())
.map(|v| !v.pre.is_empty())
.unwrap_or(false)
}
}
#[derive(Debug, Clone, Serialize)]
pub struct JsonReport<'a> {
pub schema_version: u32,
pub format: &'static str,
pub include_prerelease: bool,
pub dry_run: bool,
pub registry_url: Option<&'a str>,
pub updates_available: Vec<JsonUpdateCandidate<'a>>,
pub fresh: Vec<&'a str>,
pub skipped: Vec<JsonSkipped<'a>>,
pub version_check_errors: Vec<JsonCheckError<'a>>,
pub results: Vec<JsonResult<'a>>,
pub summary: JsonSummary,
pub aborted: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct JsonUpdateCandidate<'a> {
pub name: &'a str,
pub current: Option<&'a str>,
pub latest: &'a str,
pub source: &'static str,
pub prerelease: bool,
pub binstall: Option<&'static str>,
}
#[derive(Debug, Clone, Serialize)]
pub struct JsonSkipped<'a> {
pub name: &'a str,
pub source: &'static str,
pub reason_code: &'static str,
pub reason: &'static str,
}
#[derive(Debug, Clone, Serialize)]
pub struct JsonCheckError<'a> {
pub name: &'a str,
pub kind: &'static str,
pub error: &'a str,
}
#[derive(Debug, Clone, Serialize)]
pub struct JsonResult<'a> {
pub name: &'a str,
pub old_version: Option<&'a str>,
pub new_version: Option<&'a str>,
pub success: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct JsonSummary {
pub checked: usize,
pub available: usize,
pub selected: usize,
pub attempted: usize,
pub succeeded: usize,
pub failed: usize,
pub skipped: usize,
pub check_errors: usize,
pub duration_ms: u128,
}
impl PackageSource {
pub fn kind_str(&self) -> &'static str {
match self {
PackageSource::Crates => "crates",
PackageSource::Git { .. } => "git",
PackageSource::Path { .. } => "path",
PackageSource::Unknown(_) => "unknown",
}
}
}
impl UpdateResult {
pub fn new(
package_name: String,
old_version: Option<String>,
new_version: Option<String>,
success: bool,
) -> Self {
Self {
package_name,
old_version,
new_version,
success,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn install_opts_default_is_default() {
let o = InstallOpts::default();
assert!(o.is_default());
}
#[test]
fn install_opts_with_features_is_not_default() {
let o = InstallOpts {
no_default_features: false,
all_features: false,
features: vec!["pcre2".to_string()],
};
assert!(!o.is_default());
}
#[test]
fn install_opts_no_default_features_is_not_default() {
let o = InstallOpts {
no_default_features: true,
all_features: false,
features: vec![],
};
assert!(!o.is_default());
}
#[test]
fn install_opts_all_features_is_not_default() {
let o = InstallOpts {
no_default_features: false,
all_features: true,
features: vec![],
};
assert!(!o.is_default());
}
#[test]
fn package_info_install_opts_defaults_none() {
let p = PackageInfo::new("ripgrep".to_string(), Some("14.0.0".to_string()));
assert!(p.install_opts.is_none());
}
#[test]
fn skip_reason_code_maps_each_source() {
assert_eq!(
PackageSource::Path { dir: "/x".into() }.skip_reason_code(),
"path_source"
);
assert_eq!(
PackageSource::Git { url: "u".into(), rev: None }.skip_reason_code(),
"git_source"
);
assert_eq!(
PackageSource::Unknown("weird".into()).skip_reason_code(),
"unknown_source"
);
}
#[test]
fn check_error_kind_str_maps_both_variants() {
assert_eq!(CheckErrorKind::NotFound.kind_str(), "not_found");
assert_eq!(CheckErrorKind::Unavailable.kind_str(), "unavailable");
}
}