use crate::errors::{StoreVersionParseError, VersionParseError};
use crate::resolver::{DiffRecommendation, ViolationConflict};
use crate::serialization::spanned::Spanned;
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 cargo_metadata::semver;
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;
#[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,
})
}
}
}
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<'de> Visitor<'de> 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>>;
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
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,
}
#[derive(serde::Deserialize, Clone, Debug)]
pub struct ForeignAuditsFile {
#[serde(default)]
pub criteria: SortedMap<CriteriaName, toml::Value>,
#[serde(default)]
#[serde(rename = "wildcard-audits")]
pub wildcard_audits: SortedMap<PackageName, Vec<toml::Value>>,
#[serde(default)]
pub audits: SortedMap<PackageName, Vec<toml::Value>>,
}
#[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 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
}
}
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 },
}
#[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<'de> Visitor<'de> 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),
}
}
}
#[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(rename = "user-id")]
pub user_id: CratesUserId,
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>>,
#[serde(skip)]
pub is_fresh_import: bool,
}
impl WildcardEntry {
pub fn same_audit_as(&self, other: &WildcardEntry) -> bool {
self.user_id == other.user_id
&& self.start == other.start
&& self.end == other.end
&& self.criteria == other.criteria
}
}
#[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>>,
}
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<F: FnOnce() -> Vec<VetVersion>>(
&mut self,
name: PackageName,
version: Option<&VetVersion>,
all_versions: F,
) -> Option<&mut PolicyEntry> {
let pkg_policy = self.package.entry(name).or_insert_with(|| {
if version.is_none() {
PackagePolicyEntry::Unversioned(Default::default())
} else {
PackagePolicyEntry::Versioned {
version: all_versions()
.into_iter()
.map(|v| (v, Default::default()))
.collect(),
}
}
});
match (pkg_policy, version) {
(PackagePolicyEntry::Unversioned(e), None) => Some(e),
(PackagePolicyEntry::Versioned { version }, Some(v)) => version.get_mut(v),
_ => None,
}
}
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),
}
#[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 {
pub url: 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<'de> Visitor<'de> 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 publisher: SortedMap<PackageName, Vec<CratesPublisher>>,
#[serde(default)]
#[serde(skip_serializing_if = "SortedMap::is_empty")]
pub audits: SortedMap<ImportName, AuditsFile>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct CratesPublisher {
pub version: VetVersion,
pub when: chrono::NaiveDate,
#[serde(rename = "user-id")]
pub user_id: CratesUserId,
#[serde(rename = "user-login")]
pub user_login: String,
#[serde(rename = "user-name")]
pub user_name: Option<String>,
#[serde(skip)]
pub is_fresh_import: bool,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "version")]
pub enum DiffCache {
#[serde(rename = "2")]
V2 {
diffs: SortedMap<PackageName, SortedMap<Delta, DiffStat>>,
},
}
impl Default for DiffCache {
fn default() -> Self {
DiffCache::V2 {
diffs: SortedMap::new(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
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 {
#[serde(flatten)]
pub last_fetch: Option<FetchCommand>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PublisherCacheUser {
pub login: String,
pub name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PublisherCacheVersion {
pub created_at: chrono::DateTime<chrono::Utc>,
pub num: semver::Version,
pub published_by: Option<CratesUserId>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PublisherCacheEntry {
pub last_fetched: chrono::DateTime<chrono::Utc>,
pub versions: Vec<PublisherCacheVersion>,
}
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct PublisherCache {
pub users: SortedMap<CratesUserId, PublisherCacheUser>,
pub crates: SortedMap<PackageName, PublisherCacheEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CratesAPIUser {
pub id: CratesUserId,
pub login: String,
pub name: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CratesAPIVersion {
pub created_at: chrono::DateTime<chrono::Utc>,
pub num: semver::Version,
pub published_by: Option<CratesAPIUser>,
}
#[derive(Serialize, Deserialize, Debug, 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>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RegistryFile {
pub registry: SortedMap<ImportName, RegistryEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RegistryEntry {
pub url: 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"),
}
}
}