use std::collections::BTreeMap;
use semver::VersionReq;
use serde::{Deserialize, Serialize};
use crate::cli::BinstallChoice;
#[expect(
clippy::inline_always,
reason = "A simple literal is a perfect candidate for inlining."
)]
#[inline(always)]
const fn serde_default_true() -> bool {
true
}
#[expect(
clippy::struct_excessive_bools,
reason = "This is configuration, so needs to represent all possible items, which includes flags."
)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DetailedPackageReq {
pub version: VersionReq,
#[serde(default = "serde_default_true")]
pub default_features: bool,
#[serde(default)]
pub all_features: bool,
#[serde(default)]
pub features: Vec<String>,
#[serde(default)]
pub index: Option<String>,
#[serde(default)]
pub registry: Option<String>,
#[serde(default)]
pub git: Option<String>,
#[serde(default)]
pub branch: Option<String>,
#[serde(default)]
pub tag: Option<String>,
#[serde(default)]
pub rev: Option<String>,
#[serde(default)]
pub path: Option<String>,
#[serde(default)]
pub bins: Vec<String>,
#[serde(default)]
pub all_bins: bool,
#[serde(default)]
pub examples: Vec<String>,
#[serde(default)]
pub all_examples: bool,
#[serde(default)]
pub force: bool,
#[serde(default)]
pub ignore_rust_version: bool,
#[serde(default)]
pub frozen: bool,
#[serde(default)]
pub locked: bool,
#[serde(default)]
pub offline: bool,
#[serde(default)]
pub extra_arguments: Vec<String>,
#[serde(default)]
pub environment: BTreeMap<String, String>,
#[serde(default)]
pub skip_check: bool,
#[serde(default)]
pub no_fail_fast: bool,
#[serde(default)]
pub binstall: Option<BinstallChoice>,
}
impl Default for DetailedPackageReq {
fn default() -> Self {
Self {
default_features: true,
version: VersionReq::default(),
all_features: bool::default(),
features: Vec::default(),
index: Option::default(),
registry: Option::default(),
git: Option::default(),
branch: Option::default(),
tag: Option::default(),
rev: Option::default(),
path: Option::default(),
bins: Vec::default(),
all_bins: bool::default(),
examples: Vec::default(),
all_examples: bool::default(),
force: bool::default(),
ignore_rust_version: bool::default(),
frozen: bool::default(),
locked: bool::default(),
offline: bool::default(),
extra_arguments: Vec::default(),
environment: BTreeMap::default(),
skip_check: bool::default(),
no_fail_fast: bool::default(),
binstall: Option::default(),
}
}
}
impl From<PackageRequirement> for DetailedPackageReq {
fn from(pkg: PackageRequirement) -> Self {
match pkg {
PackageRequirement::Simple(pkg_ver) => Self {
version: pkg_ver,
..Default::default()
},
PackageRequirement::Detailed(det_pkg) => *det_pkg,
}
}
}
impl DetailedPackageReq {
pub fn effective_skip_check(&self) -> bool {
self.skip_check
|| self.path.is_some()
|| self.git.is_some()
|| self.branch.is_some()
|| self.tag.is_some()
|| self.rev.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PackageRequirement {
#[expect(
clippy::doc_markdown,
reason = "SemVer is not a Rust type here, but a proper noun."
)]
Simple(VersionReq),
Detailed(Box<DetailedPackageReq>),
}
impl PackageRequirement {
pub const SIMPLE_STAR: Self = Self::Simple(VersionReq::STAR);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detailedpackagereq_effectiveskipcheck_default() {
assert!(!DetailedPackageReq::default().effective_skip_check());
}
#[test]
fn test_detailedpackagereq_effectiveskipcheck_skipcheck() {
assert!(
DetailedPackageReq {
skip_check: true,
..Default::default()
}
.effective_skip_check()
);
}
#[test]
fn test_detailedpackagereq_effectiveskipcheck_path() {
assert!(
DetailedPackageReq {
path: Some("/a/b/c".to_owned()),
..Default::default()
}
.effective_skip_check()
);
}
#[test]
fn test_detailedpackagereq_effectiveskipcheck_git() {
assert!(
DetailedPackageReq {
git: Some("ssh://git@example.com/user/repo.git".to_owned()),
..Default::default()
}
.effective_skip_check()
);
}
#[test]
fn test_detailedpackagereq_effectiveskipcheck_branch() {
assert!(
DetailedPackageReq {
branch: Some("example".to_owned()),
..Default::default()
}
.effective_skip_check()
);
}
#[test]
fn test_detailedpackagereq_effectiveskipcheck_tag() {
assert!(
DetailedPackageReq {
tag: Some("example".to_owned()),
..Default::default()
}
.effective_skip_check()
);
}
#[test]
fn test_detailedpackagereq_effectiveskipcheck_rev() {
assert!(
DetailedPackageReq {
rev: Some("1337133713371337133713371337133713371337".to_owned()),
..Default::default()
}
.effective_skip_check()
);
}
#[test]
fn test_detailedpackagereq_effectiveskipcheck_combination_1() {
assert!(
DetailedPackageReq {
skip_check: true,
path: Some("/a/b/c".to_owned()),
..Default::default()
}
.effective_skip_check()
);
}
#[test]
fn test_detailedpackagereq_effectiveskipcheck_combination_2() {
assert!(
DetailedPackageReq {
git: Some("ssh://git@example.com/user/repo.git".to_owned()),
branch: Some("example".to_owned()),
..Default::default()
}
.effective_skip_check()
);
}
}