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::Visitor;
use serde::{de, 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::Deserialize, Clone)]
pub struct ForeignAuditsFile {
#[serde(default)]
pub criteria: SortedMap<CriteriaName, toml::Value>,
pub audits: SortedMap<PackageName, Vec<toml::Value>>,
}
#[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>,
#[serde(skip)]
pub is_fresh_import: bool,
}
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 {
from: Version,
to: Version,
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: Option<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, 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(Version::parse(from.trim()).map_err(de::Error::custom)?),
to: Version::parse(to.trim()).map_err(de::Error::custom)?,
})
} else {
Ok(Delta {
from: None,
to: Version::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(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, 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(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
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>,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "version")]
pub enum DiffCache {
#[serde(rename = "1")]
V1 {
diffs: SortedMap<PackageName, SortedMap<Delta, DiffStat>>,
},
}
impl Default for DiffCache {
fn default() -> Self {
DiffCache::V1 {
diffs: SortedMap::new(),
}
}
}
#[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>,
}