use crate::cli::FetchMode;
use crate::errors::{StoreVersionParseError, VersionParseError};
use crate::resolver::{DiffRecommendation, ViolationConflict};
use crate::serialization::{spanned::Spanned, CacheFileVersion, Tidyable};
use crate::{flock::Filesystem, serialization};
use core::{cmp, fmt};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use cargo_metadata::{semver, Package};
use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
pub type FastMap<K, V> = HashMap<K, V>;
pub type FastSet<T> = HashSet<T>;
pub type SortedMap<K, V> = BTreeMap<K, V>;
pub type SortedSet<T> = BTreeSet<T>;
pub type CriteriaName = String;
pub type CriteriaStr<'a> = &'a str;
pub type ForeignCriteriaName = String;
pub type PackageName = String;
pub type PackageStr<'a> = &'a str;
pub type ImportName = String;
pub type ImportStr<'a> = &'a str;
pub type CratesUserId = u64;
pub type CratesTrustpubSignature = String;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct VersionReq(pub semver::VersionReq);
impl fmt::Display for VersionReq {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl FromStr for VersionReq {
type Err = <semver::VersionReq as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
semver::VersionReq::from_str(s).map(VersionReq)
}
}
impl core::ops::Deref for VersionReq {
type Target = semver::VersionReq;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl cmp::PartialOrd for VersionReq {
fn partial_cmp(&self, other: &VersionReq) -> Option<cmp::Ordering> {
format!("{self}").partial_cmp(&format!("{other}"))
}
}
impl VersionReq {
pub fn parse(text: &str) -> Result<Self, <Self as FromStr>::Err> {
cargo_metadata::semver::VersionReq::parse(text).map(VersionReq)
}
pub fn matches(&self, version: &VetVersion) -> bool {
self.0.matches(&version.semver)
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VetVersion {
pub semver: semver::Version,
pub git_rev: Option<String>,
}
impl VetVersion {
pub fn parse(s: &str) -> Result<Self, VersionParseError> {
if let Some((ver, rev)) = s.split_once('@') {
if let Some(hash) = rev.trim_start().strip_prefix("git:") {
if hash.len() != 40 || !hash.bytes().all(|b| b.is_ascii_hexdigit()) {
Err(VersionParseError::InvalidGitHash)
} else {
Ok(VetVersion {
semver: ver.trim_end().parse()?,
git_rev: Some(hash.to_owned()),
})
}
} else {
Err(VersionParseError::UnknownRevision)
}
} else {
Ok(VetVersion {
semver: s.parse()?,
git_rev: None,
})
}
}
pub fn equals_semver(&self, semver: &semver::Version) -> bool {
self.git_rev.is_none() && &self.semver == semver
}
pub fn as_semver(&self) -> Option<&semver::Version> {
if self.git_rev.is_none() {
Some(&self.semver)
} else {
None
}
}
}
impl fmt::Display for VetVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.git_rev {
Some(hash) => write!(f, "{}@git:{}", self.semver, hash),
None => self.semver.fmt(f),
}
}
}
impl FromStr for VetVersion {
type Err = VersionParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl Serialize for VetVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for VetVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct VersionVisitor;
impl Visitor<'_> for VersionVisitor {
type Value = VetVersion;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("semver version")
}
fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
VetVersion::parse(string).map_err(de::Error::custom)
}
}
deserializer.deserialize_str(VersionVisitor)
}
}
#[derive(serde::Deserialize)]
pub struct MetaConfigInstance {
pub version: Option<u64>,
pub store: Option<StoreInfo>,
}
#[derive(serde::Deserialize)]
pub struct StoreInfo {
pub path: Option<PathBuf>,
}
pub struct MetaConfig(pub Vec<MetaConfigInstance>);
impl MetaConfig {
pub fn store_path(&self) -> Filesystem {
for config in self.0.iter().rev() {
if let Some(store) = &config.store {
if let Some(path) = &store.path {
return Filesystem::new(path.into());
}
}
}
unreachable!("Default config didn't define store.path???");
}
pub fn version(&self) -> u64 {
for config in self.0.iter().rev() {
if let Some(ver) = config.version {
return ver;
}
}
unreachable!("Default config didn't define version???");
}
}
pub type WildcardAudits = SortedMap<PackageName, Vec<WildcardEntry>>;
pub type AuditedDependencies = SortedMap<PackageName, Vec<AuditEntry>>;
pub type TrustedPackages = SortedMap<PackageName, Vec<TrustEntry>>;
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, Default)]
pub struct AuditsFile {
#[serde(skip_serializing_if = "SortedMap::is_empty")]
#[serde(default)]
pub criteria: SortedMap<CriteriaName, CriteriaEntry>,
#[serde(rename = "wildcard-audits")]
#[serde(skip_serializing_if = "SortedMap::is_empty")]
#[serde(default)]
pub wildcard_audits: WildcardAudits,
pub audits: AuditedDependencies,
#[serde(skip_serializing_if = "SortedMap::is_empty")]
#[serde(default)]
pub trusted: TrustedPackages,
}
impl Tidyable for AuditsFile {
fn tidy(&mut self) {
self.audits.tidy();
self.wildcard_audits.tidy();
self.trusted.tidy();
}
}
#[derive(serde::Deserialize, Clone, Debug)]
pub struct ForeignAuditsFile {
#[serde(default)]
pub criteria: SortedMap<CriteriaName, toml::Table>,
#[serde(default)]
#[serde(rename = "wildcard-audits")]
pub wildcard_audits: SortedMap<PackageName, Vec<toml::Table>>,
#[serde(default)]
pub audits: SortedMap<PackageName, Vec<toml::Table>>,
#[serde(default)]
pub trusted: SortedMap<PackageName, Vec<toml::Table>>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct CriteriaEntry {
pub description: Option<String>,
#[serde(rename = "description-url")]
pub description_url: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
#[serde(with = "serialization::string_or_vec")]
pub implies: Vec<Spanned<CriteriaName>>,
#[serde(rename = "aggregated-from")]
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
#[serde(with = "serialization::string_or_vec")]
pub aggregated_from: Vec<Spanned<String>>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(try_from = "serialization::audit::AuditEntryAll")]
#[serde(into = "serialization::audit::AuditEntryAll")]
pub struct AuditEntry {
pub who: Vec<Spanned<String>>,
pub criteria: Vec<Spanned<CriteriaName>>,
pub kind: AuditKind,
pub importable: bool,
pub notes: Option<String>,
pub aggregated_from: Vec<Spanned<String>>,
#[serde(skip)]
pub is_fresh_import: bool,
}
impl AuditEntry {
pub fn same_audit_as(&self, other: &AuditEntry) -> bool {
self.kind == other.kind && self.criteria == other.criteria
}
pub fn try_collapse_with_prior(&self, other: &AuditEntry) -> Option<Self> {
let AuditKind::Delta {
from: self_from,
to: self_to,
} = &self.kind
else {
return None;
};
let (other_from, other_to) = match &other.kind {
AuditKind::Full { version } => (None, version),
AuditKind::Delta { from, to } => (Some(from), to),
AuditKind::Violation { .. } => return None,
};
if other_to != self_from {
return None;
}
if other.criteria != self.criteria {
return None;
}
let mut new_entry = self.clone();
new_entry.kind = match other_from {
Some(version) => AuditKind::Delta {
from: version.clone(),
to: self_to.clone(),
},
None => AuditKind::Full {
version: self_to.clone(),
},
};
let unique_who: Vec<_> = other
.who
.iter()
.filter(|s| !new_entry.who.contains(s))
.cloned()
.collect();
new_entry.who.splice(..0, unique_who);
if let Some(prior_notes) = &other.notes {
new_entry.notes = Some(match new_entry.notes {
None => prior_notes.clone(),
Some(new_notes) => format!("{prior_notes}\n{new_notes}"),
});
}
new_entry.importable = new_entry.kind.default_importable();
Some(new_entry)
}
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl cmp::PartialOrd for AuditEntry {
fn partial_cmp<'a>(&'a self, other: &'a AuditEntry) -> Option<cmp::Ordering> {
let tuple = |x: &'a AuditEntry| (&x.kind, &x.criteria, &x.who, &x.notes);
tuple(self).partial_cmp(&tuple(other))
}
}
impl cmp::Ord for AuditEntry {
fn cmp(&self, other: &AuditEntry) -> cmp::Ordering {
self.partial_cmp(other).unwrap()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
pub enum AuditKind {
Full { version: VetVersion },
Delta { from: VetVersion, to: VetVersion },
Violation { violation: VersionReq },
}
impl AuditKind {
pub fn default_importable(&self) -> bool {
match self {
Self::Full { version } => version.git_rev.is_none(),
Self::Delta { from, to } => from.git_rev.is_none() && to.git_rev.is_none(),
Self::Violation { .. } => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Delta {
pub from: Option<VetVersion>,
pub to: VetVersion,
}
impl<'de> Deserialize<'de> for Delta {
fn deserialize<D>(deserializer: D) -> Result<Delta, D::Error>
where
D: Deserializer<'de>,
{
struct DeltaVisitor;
impl Visitor<'_> for DeltaVisitor {
type Value = Delta;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("version -> version delta")
}
fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if let Some((from, to)) = string.split_once("->") {
Ok(Delta {
from: Some(VetVersion::parse(from.trim()).map_err(de::Error::custom)?),
to: VetVersion::parse(to.trim()).map_err(de::Error::custom)?,
})
} else {
Ok(Delta {
from: None,
to: VetVersion::parse(string.trim()).map_err(de::Error::custom)?,
})
}
}
}
deserializer.deserialize_str(DeltaVisitor)
}
}
impl Serialize for Delta {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self.from {
Some(from) => format!("{} -> {}", from, self.to).serialize(serializer),
None => self.to.serialize(serializer),
}
}
}
impl fmt::Display for Delta {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.from {
Some(from) => writeln!(f, "{} -> {}", from, self.to),
None => self.to.fmt(f),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(untagged)]
pub enum CratesSourceId {
User {
#[serde(rename = "user-id")]
user_id: CratesUserId,
},
TrustedPublisher {
#[serde(rename = "trusted-publisher")]
trusted_publisher: CratesTrustpubSignature,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct WildcardEntry {
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "serialization::string_or_vec")]
pub who: Vec<Spanned<String>>,
#[serde(with = "serialization::string_or_vec")]
pub criteria: Vec<Spanned<CriteriaName>>,
#[serde(flatten)]
pub source: CratesSourceId,
pub start: Spanned<chrono::NaiveDate>,
pub end: Spanned<chrono::NaiveDate>,
pub renew: Option<bool>,
pub notes: Option<String>,
#[serde(rename = "aggregated-from")]
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "serialization::string_or_vec")]
#[serde(default)]
pub aggregated_from: Vec<Spanned<String>>,
#[serde(skip)]
pub is_fresh_import: bool,
}
impl WildcardEntry {
pub fn same_audit_as(&self, other: &WildcardEntry) -> bool {
self.source == other.source
&& self.start == other.start
&& self.end == other.end
&& self.criteria == other.criteria
}
pub fn should_renew(&self, date: chrono::NaiveDate) -> bool {
self.renew.unwrap_or(true) && self.end < date
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct TrustEntry {
#[serde(with = "serialization::string_or_vec")]
pub criteria: Vec<Spanned<CriteriaName>>,
#[serde(flatten)]
pub source: CratesSourceId,
pub start: Spanned<chrono::NaiveDate>,
pub end: Spanned<chrono::NaiveDate>,
pub notes: Option<String>,
#[serde(rename = "aggregated-from")]
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "serialization::string_or_vec")]
#[serde(default)]
pub aggregated_from: Vec<Spanned<String>>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
pub struct ConfigFile {
#[serde(rename = "cargo-vet")]
#[serde(default = "CargoVetConfig::missing")]
pub cargo_vet: CargoVetConfig,
#[serde(rename = "default-criteria")]
#[serde(default = "get_default_criteria")]
#[serde(skip_serializing_if = "is_default_criteria")]
pub default_criteria: CriteriaName,
#[serde(skip_serializing_if = "SortedMap::is_empty")]
#[serde(default)]
pub imports: SortedMap<ImportName, RemoteImport>,
#[serde(skip_serializing_if = "Policy::is_empty")]
#[serde(default)]
pub policy: Policy,
#[serde(skip_serializing_if = "SortedMap::is_empty")]
#[serde(default)]
#[serde(alias = "unaudited")]
pub exemptions: SortedMap<PackageName, Vec<ExemptedDependency>>,
}
impl Tidyable for ConfigFile {
fn tidy(&mut self) {
self.exemptions.tidy();
}
}
pub static SAFE_TO_DEPLOY: CriteriaStr = "safe-to-deploy";
pub static SAFE_TO_RUN: CriteriaStr = "safe-to-run";
pub static DEFAULT_CRITERIA: CriteriaStr = SAFE_TO_DEPLOY;
pub fn get_default_criteria() -> CriteriaName {
CriteriaName::from(DEFAULT_CRITERIA)
}
fn is_default_criteria(val: &CriteriaName) -> bool {
val == DEFAULT_CRITERIA
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
#[serde(try_from = "serialization::policy::AllPolicies")]
#[serde(into = "serialization::policy::AllPolicies")]
pub struct Policy {
pub package: SortedMap<PackageName, PackagePolicyEntry>,
}
impl Policy {
pub fn get(&self, name: PackageStr, version: &VetVersion) -> Option<&PolicyEntry> {
self.package
.get(name)
.and_then(|pkg_policy| match pkg_policy {
PackagePolicyEntry::Unversioned(e) => Some(e),
PackagePolicyEntry::Versioned { version: v } => v.get(version),
})
}
pub fn get_mut(
&mut self,
name: PackageStr,
version: Option<&VetVersion>,
) -> Option<&mut PolicyEntry> {
self.package
.get_mut(name)
.and_then(|pkg_policy| match pkg_policy {
PackagePolicyEntry::Unversioned(e) => Some(e),
PackagePolicyEntry::Versioned { version: v } => {
version.and_then(|version| v.get_mut(version))
}
})
}
pub fn get_mut_or_default(
&mut self,
name: PackageName,
version: &VetVersion,
require_versions: Option<Vec<VetVersion>>,
) -> &mut PolicyEntry {
let pkg_policy = self.package.entry(name).or_default();
if let Some(require_versions) = require_versions {
let mut default = PolicyEntry::default();
let versions_map = loop {
match pkg_policy {
PackagePolicyEntry::Unversioned(entry) => {
default = std::mem::take(entry);
*pkg_policy = PackagePolicyEntry::Versioned {
version: Default::default(),
}
}
PackagePolicyEntry::Versioned { version } => break version,
}
};
for version in require_versions {
versions_map
.entry(version)
.or_insert_with(|| default.clone());
}
}
match pkg_policy {
PackagePolicyEntry::Unversioned(e) => e,
PackagePolicyEntry::Versioned {
version: versions_map,
} => {
versions_map.entry(version.clone()).or_default()
}
}
}
pub fn insert(
&mut self,
name: PackageName,
entry: PackagePolicyEntry,
) -> Option<PackagePolicyEntry> {
self.package.insert(name, entry)
}
pub fn is_empty(&self) -> bool {
self.package.is_empty()
}
pub fn iter(&self) -> PolicyIter<'_> {
PolicyIter {
iter: self.package.iter(),
versioned: None,
}
}
}
pub struct PolicyIter<'a> {
iter: <&'a SortedMap<PackageName, PackagePolicyEntry> as IntoIterator>::IntoIter,
versioned: Option<(
&'a PackageName,
<&'a SortedMap<VetVersion, PolicyEntry> as IntoIterator>::IntoIter,
)>,
}
impl<'a> Iterator for PolicyIter<'a> {
type Item = (&'a PackageName, Option<&'a VetVersion>, &'a PolicyEntry);
fn next(&mut self) -> Option<Self::Item> {
match &mut self.versioned {
Some((name, versioned)) => match versioned.next() {
Some((v, p)) => Some((name, Some(v), p)),
None => {
self.versioned = None;
self.next()
}
},
None => {
let (name, ppe) = self.iter.next()?;
match ppe {
PackagePolicyEntry::Versioned { version } => {
self.versioned = Some((name, version.iter()));
self.next()
}
PackagePolicyEntry::Unversioned(p) => Some((name, None, p)),
}
}
}
}
}
impl<'a> IntoIterator for &'a Policy {
type IntoIter = PolicyIter<'a>;
type Item = <PolicyIter<'a> as Iterator>::Item;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Clone)]
pub enum PackagePolicyEntry {
Versioned {
version: SortedMap<VetVersion, PolicyEntry>,
},
Unversioned(PolicyEntry),
}
impl Default for PackagePolicyEntry {
fn default() -> Self {
PackagePolicyEntry::Unversioned(Default::default())
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
pub struct PolicyEntry {
#[serde(rename = "audit-as-crates-io")]
pub audit_as_crates_io: Option<bool>,
#[serde(default)]
#[serde(with = "serialization::string_or_vec_or_none")]
pub criteria: Option<Vec<Spanned<CriteriaName>>>,
#[serde(rename = "dev-criteria")]
#[serde(default)]
#[serde(with = "serialization::string_or_vec_or_none")]
pub dev_criteria: Option<Vec<Spanned<CriteriaName>>>,
#[serde(rename = "dependency-criteria")]
#[serde(skip_serializing_if = "CriteriaMap::is_empty")]
#[serde(with = "serialization::criteria_map")]
#[serde(default)]
pub dependency_criteria: CriteriaMap,
pub notes: Option<String>,
}
pub type CriteriaMap = SortedMap<Spanned<String>, Vec<Spanned<CriteriaName>>>;
pub static DEFAULT_POLICY_CRITERIA: CriteriaStr = SAFE_TO_DEPLOY;
pub static DEFAULT_POLICY_DEV_CRITERIA: CriteriaStr = SAFE_TO_RUN;
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
pub struct RemoteImport {
#[serde(with = "serialization::string_or_vec")]
pub url: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub exclude: Vec<PackageName>,
#[serde(rename = "criteria-map")]
#[serde(skip_serializing_if = "CriteriaMap::is_empty")]
#[serde(with = "serialization::criteria_map")]
#[serde(default)]
pub criteria_map: CriteriaMap,
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
pub struct CriteriaMapping {
pub ours: CriteriaName,
pub theirs: Spanned<ForeignCriteriaName>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExemptedDependency {
pub version: VetVersion,
#[serde(default)]
#[serde(with = "serialization::string_or_vec")]
pub criteria: Vec<Spanned<CriteriaName>>,
#[serde(default = "get_default_exemptions_suggest")]
#[serde(skip_serializing_if = "is_default_exemptions_suggest")]
pub suggest: bool,
pub notes: Option<String>,
}
static DEFAULT_EXEMPTIONS_SUGGEST: bool = true;
pub fn get_default_exemptions_suggest() -> bool {
DEFAULT_EXEMPTIONS_SUGGEST
}
fn is_default_exemptions_suggest(val: &bool) -> bool {
val == &DEFAULT_EXEMPTIONS_SUGGEST
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct StoreVersion {
pub major: u64,
pub minor: u64,
}
impl StoreVersion {
#[cfg(not(test))]
pub fn current() -> Self {
StoreVersion {
major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
}
}
#[cfg(test)]
pub fn current() -> Self {
StoreVersion { major: 1, minor: 0 }
}
}
impl FromStr for StoreVersion {
type Err = StoreVersionParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_once('.') {
Some((major, minor)) => Ok(StoreVersion {
major: major.parse()?,
minor: minor.parse()?,
}),
None => Err(StoreVersionParseError::MissingSeparator),
}
}
}
impl fmt::Display for StoreVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}
impl Serialize for StoreVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for StoreVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct VersionVisitor;
impl Visitor<'_> for VersionVisitor {
type Value = StoreVersion;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("store version")
}
fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
StoreVersion::from_str(string).map_err(de::Error::custom)
}
}
deserializer.deserialize_str(VersionVisitor)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CargoVetConfig {
pub version: StoreVersion,
}
impl CargoVetConfig {
fn missing() -> Self {
Self {
version: StoreVersion { major: 0, minor: 4 },
}
}
}
impl Default for CargoVetConfig {
fn default() -> Self {
Self {
version: StoreVersion::current(),
}
}
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct ImportsFile {
#[serde(default)]
#[serde(skip_serializing_if = "SortedMap::is_empty")]
pub unpublished: SortedMap<PackageName, Vec<UnpublishedEntry>>,
#[serde(default)]
#[serde(skip_serializing_if = "SortedMap::is_empty")]
pub publisher: SortedMap<PackageName, Vec<CratesPublisher>>,
#[serde(default)]
#[serde(skip_serializing_if = "SortedMap::is_empty")]
pub audits: SortedMap<ImportName, AuditsFile>,
}
impl Tidyable for ImportsFile {
fn tidy(&mut self) {
self.unpublished.tidy();
self.publisher.tidy();
for audits_file in self.audits.values_mut() {
audits_file.tidy();
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[serde(untagged)]
pub enum CratesPublisherSource {
User {
#[serde(rename = "user-id")]
user_id: CratesUserId,
#[serde(rename = "user-login")]
user_login: String,
#[serde(rename = "user-name")]
user_name: Option<String>,
},
TrustedPublisher {
#[serde(rename = "trusted-publisher")]
trusted_publisher: CratesTrustpubSignature,
},
}
impl CratesPublisherSource {
pub fn as_identifier(&self) -> &str {
match self {
CratesPublisherSource::User { user_login, .. } => &user_login[..],
CratesPublisherSource::TrustedPublisher { trusted_publisher } => &trusted_publisher[..],
}
}
pub fn as_wildcard_source(&self) -> CratesSourceId {
match self {
CratesPublisherSource::User { user_id, .. } => {
CratesSourceId::User { user_id: *user_id }
}
CratesPublisherSource::TrustedPublisher { trusted_publisher } => {
CratesSourceId::TrustedPublisher {
trusted_publisher: trusted_publisher.clone(),
}
}
}
}
}
impl fmt::Display for CratesPublisherSource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CratesPublisherSource::User {
user_login,
user_name: Some(user_name),
..
} => write!(f, "{} ({})", user_name, user_login),
CratesPublisherSource::User { user_login, .. } => write!(f, "{}", user_login),
CratesPublisherSource::TrustedPublisher { trusted_publisher } => {
write!(f, "{}", trusted_publisher)
}
}
}
}
impl PartialEq<CratesSourceId> for CratesPublisherSource {
fn eq(&self, other: &CratesSourceId) -> bool {
match (self, other) {
(
CratesPublisherSource::User { user_id: us, .. },
CratesSourceId::User { user_id: them },
) => us == them,
(
CratesPublisherSource::TrustedPublisher {
trusted_publisher: us,
},
CratesSourceId::TrustedPublisher {
trusted_publisher: them,
},
) => us == them,
_ => false,
}
}
}
impl PartialEq<CratesPublisherSource> for CratesSourceId {
fn eq(&self, other: &CratesPublisherSource) -> bool {
other == self
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct CratesPublisher {
pub version: VetVersion,
pub when: chrono::NaiveDate,
#[serde(flatten)]
pub source: CratesPublisherSource,
#[serde(skip)]
pub is_fresh_import: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct UnpublishedEntry {
pub version: VetVersion,
pub audited_as: VetVersion,
#[serde(skip)]
pub still_unpublished: bool,
#[serde(skip)]
pub is_fresh_import: bool,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct DiffCache {
pub version: CacheFileVersion<2>,
pub diffs: SortedMap<PackageName, SortedMap<Delta, DiffStat>>,
}
impl Tidyable for DiffCache {
fn tidy(&mut self) {}
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct DiffStat {
pub insertions: u64,
pub deletions: u64,
pub files_changed: u64,
}
impl DiffStat {
pub fn count(&self) -> u64 {
self.insertions + self.deletions
}
}
impl fmt::Display for DiffStat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} files changed", self.files_changed)?;
if self.insertions > 0 {
write!(f, ", {} insertions(+)", self.insertions)?;
}
if self.deletions > 0 {
write!(f, ", {} deletions(-)", self.deletions)?;
}
Ok(())
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum FetchCommand {
Inspect {
package: PackageName,
version: VetVersion,
},
Diff {
package: PackageName,
version1: VetVersion,
version2: VetVersion,
},
}
impl FetchCommand {
pub fn package(&self) -> PackageStr<'_> {
match self {
FetchCommand::Inspect { package, .. } => package,
FetchCommand::Diff { package, .. } => package,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct CommandHistory {
pub version: CacheFileVersion<1>,
#[serde(flatten)]
pub last_fetch: Option<FetchCommand>,
pub last_diff_fetch_mode: Option<FetchMode>,
pub last_inspect_fetch_mode: Option<FetchMode>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CratesCacheUser {
pub login: String,
pub name: Option<String>,
}
impl fmt::Display for CratesCacheUser {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(name) = &self.name {
write!(f, "{} ({})", name, self.login)
} else {
write!(f, "{}", self.login)
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CratesCacheVersionDetails {
pub created_at: chrono::DateTime<chrono::Utc>,
pub source: Option<CratesSourceId>,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct CratesCacheEntry {
pub versions: SortedMap<semver::Version, CratesCacheVersionDetails>,
pub metadata: CratesAPICrateMetadata,
}
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct CratesCache {
pub version: CacheFileVersion<2>,
pub users: SortedMap<CratesUserId, CratesCacheUser>,
pub crates: SortedMap<PackageName, Arc<CratesCacheEntry>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CratesAPIUser {
pub id: CratesUserId,
pub login: String,
pub name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(tag = "provider")]
pub enum CratesAPITrustpubData {
#[serde(rename = "github")]
GitHub { repository: String },
#[serde(other)]
Unknown,
}
impl CratesAPITrustpubData {
pub fn as_signature(&self) -> Option<CratesTrustpubSignature> {
match self {
CratesAPITrustpubData::GitHub { repository } => Some(format!("github:{repository}")),
CratesAPITrustpubData::Unknown => None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CratesAPIVersion {
pub created_at: chrono::DateTime<chrono::Utc>,
pub num: semver::Version,
pub published_by: Option<CratesAPIUser>,
pub trustpub_data: Option<CratesAPITrustpubData>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct CratesAPICrateMetadata {
pub description: Option<String>,
pub repository: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CratesAPICrate {
#[serde(rename = "crate")]
pub crate_data: CratesAPICrateMetadata,
pub versions: Vec<CratesAPIVersion>,
}
impl CratesAPICrateMetadata {
pub fn consider_as_same(&self, p: &Package) -> bool {
(self.description.is_some() && p.description == self.description)
|| (self.repository.is_some() && p.repository == self.repository)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct RegistryFile {
pub registry: SortedMap<ImportName, RegistryEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RegistryEntry {
#[serde(with = "serialization::string_or_vec")]
pub url: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonReport {
#[serde(flatten)]
pub conclusion: JsonReportConclusion,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "conclusion")]
pub enum JsonReportConclusion {
#[serde(rename = "success")]
Success(JsonReportSuccess),
#[serde(rename = "fail (violation)")]
FailForViolationConflict(JsonReportFailForViolationConflict),
#[serde(rename = "fail (vetting)")]
FailForVet(JsonReportFailForVet),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonReportSuccess {
pub vetted_fully: Vec<JsonPackage>,
pub vetted_partially: Vec<JsonPackage>,
pub vetted_with_exemptions: Vec<JsonPackage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonReportFailForViolationConflict {
pub violations: SortedMap<PackageAndVersion, Vec<ViolationConflict>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonReportFailForVet {
pub failures: Vec<JsonVetFailure>,
pub suggest: Option<JsonSuggest>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonSuggest {
pub suggestions: Vec<JsonSuggestItem>,
pub suggest_by_criteria: SortedMap<String, Vec<JsonSuggestItem>>,
pub total_lines: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonVetFailure {
pub name: PackageName,
pub version: VetVersion,
pub missing_criteria: Vec<CriteriaName>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonSuggestItem {
pub name: PackageName,
pub notable_parents: String,
pub suggested_criteria: Vec<CriteriaName>,
pub suggested_diff: DiffRecommendation,
}
pub type PackageAndVersion = String;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonPackage {
pub name: PackageName,
pub version: VetVersion,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn vet_version_parsing() {
assert_eq!(
VetVersion::parse("1.0.0").unwrap(),
VetVersion {
semver: "1.0.0".parse().unwrap(),
git_rev: None
}
);
assert_eq!(
VetVersion::parse("1.0.1@git:00112233445566778899aabbccddeeff00112233").unwrap(),
VetVersion {
semver: "1.0.1".parse().unwrap(),
git_rev: Some("00112233445566778899aabbccddeeff00112233".into())
}
);
match VetVersion::parse("1.0.1@git:00112233445566778899aabbccddeeff0011223g") {
Err(VersionParseError::InvalidGitHash) => (),
_ => panic!("expected invalid git hash"),
}
match VetVersion::parse("1.0.1@git:00112233") {
Err(VersionParseError::InvalidGitHash) => (),
_ => panic!("expected invalid git hash"),
}
match VetVersion::parse("1.0.1@pijul:00112233") {
Err(VersionParseError::UnknownRevision) => (),
_ => panic!("expected unknown revision"),
}
}
}