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::Version;
use serde::{
de::{self, 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;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct VersionReq(pub cargo_metadata::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 = <cargo_metadata::VersionReq as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
cargo_metadata::VersionReq::from_str(s).map(VersionReq)
}
}
impl core::ops::Deref for VersionReq {
type Target = cargo_metadata::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::VersionReq::parse(text).map(VersionReq)
}
}
#[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 AuditedDependencies = SortedMap<PackageName, Vec<AuditEntry>>;
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct AuditsFile {
#[serde(skip_serializing_if = "SortedMap::is_empty")]
#[serde(default)]
pub criteria: SortedMap<CriteriaName, CriteriaEntry>,
pub audits: AuditedDependencies,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
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>>,
}
#[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: Option<String>,
pub criteria: Vec<Spanned<CriteriaName>>,
pub kind: AuditKind,
pub notes: Option<String>,
}
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: Version,
dependency_criteria: DependencyCriteria,
},
Delta {
delta: Delta,
dependency_criteria: DependencyCriteria,
},
Violation {
violation: VersionReq,
},
}
pub type DependencyCriteria = SortedMap<PackageName, Vec<Spanned<CriteriaName>>>;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Delta {
pub from: Version,
pub to: Version,
}
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, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a delta of the form 'VERSION -> VERSION'")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if let Some((from, to)) = s.split_once("->") {
Ok(Delta {
from: Version::parse(from.trim()).map_err(de::Error::custom)?,
to: Version::parse(to.trim()).map_err(de::Error::custom)?,
})
} else {
Err(de::Error::invalid_value(de::Unexpected::Str(s), &self))
}
}
}
deserializer.deserialize_str(DeltaVisitor)
}
}
impl Serialize for Delta {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let output = format!("{} -> {}", self.from, self.to);
serializer.serialize_str(&output)
}
}
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct ConfigFile {
#[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 = "SortedMap::is_empty")]
#[serde(default)]
pub policy: SortedMap<PackageName, PolicyEntry>,
#[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(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 = "DependencyCriteria::is_empty")]
#[serde(with = "serialization::dependency_criteria")]
#[serde(default)]
pub dependency_criteria: DependencyCriteria,
pub notes: Option<String>,
}
pub static DEFAULT_POLICY_CRITERIA: CriteriaStr = SAFE_TO_DEPLOY;
pub static DEFAULT_POLICY_DEV_CRITERIA: CriteriaStr = SAFE_TO_RUN;
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct RemoteImport {
pub url: String,
#[serde(rename = "criteria-map")]
pub criteria_map: Vec<CriteriaMapping>,
}
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct CriteriaMapping {
pub ours: CriteriaName,
#[serde(with = "serialization::string_or_vec")]
pub theirs: Vec<Spanned<ForeignCriteriaName>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExemptedDependency {
pub version: Version,
#[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,
#[serde(rename = "dependency-criteria")]
#[serde(skip_serializing_if = "DependencyCriteria::is_empty")]
#[serde(with = "serialization::dependency_criteria")]
#[serde(default)]
pub dependency_criteria: DependencyCriteria,
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(serde::Serialize, serde::Deserialize, Clone)]
pub struct ImportsFile {
pub audits: SortedMap<ImportName, AuditsFile>,
}
pub type DiffCache = SortedMap<PackageName, SortedMap<Delta, DiffStat>>;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DiffStat {
pub raw: String,
pub count: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum FetchCommand {
Inspect {
package: PackageName,
version: Version,
},
Diff {
package: PackageName,
version1: Version,
version2: Version,
},
}
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>,
}