use rustsec::{
Error, ErrorKind, WarningKind, advisory,
platforms::target::{Arch, OS},
report,
};
use serde::{Deserialize, Serialize};
use std::{path::PathBuf, str::FromStr};
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AuditConfig {
#[serde(default)]
pub advisories: AdvisoryConfig,
#[serde(default)]
pub database: DatabaseConfig,
#[serde(default)]
pub output: OutputConfig,
#[serde(default)]
pub target: TargetConfig,
#[serde(default)]
pub yanked: YankedConfig,
}
impl AuditConfig {
pub fn report_settings(&self) -> report::Settings {
let mut settings = report::Settings {
ignore: self.advisories.ignore.clone(),
severity: self.advisories.severity_threshold,
target_arch: self.target.arch(),
target_os: self.target.os(),
..Default::default()
};
if let Some(informational_warnings) = &self.advisories.informational_warnings {
settings
.informational_warnings
.clone_from(informational_warnings);
} else {
settings.informational_warnings = vec![
advisory::Informational::Unmaintained,
advisory::Informational::Unsound,
advisory::Informational::Notice,
];
}
let mut insert_if_not_present = |warning| {
if !settings.informational_warnings.contains(&warning) {
settings.informational_warnings.push(warning);
}
};
for deny in &self.output.deny {
match deny {
DenyOption::Warnings => {
insert_if_not_present(advisory::Informational::Notice);
insert_if_not_present(advisory::Informational::Unmaintained);
insert_if_not_present(advisory::Informational::Unsound);
break;
}
DenyOption::Unmaintained => {
insert_if_not_present(advisory::Informational::Unmaintained)
}
DenyOption::Unsound => insert_if_not_present(advisory::Informational::Unsound),
DenyOption::Yanked => continue,
};
}
settings
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AdvisoryConfig {
#[serde(default)]
pub ignore: Vec<advisory::Id>,
pub informational_warnings: Option<Vec<advisory::Informational>>,
pub severity_threshold: Option<advisory::Severity>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DatabaseConfig {
pub path: Option<PathBuf>,
pub url: Option<String>,
#[serde(default = "default_true")]
pub fetch: bool,
pub stale: bool,
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
path: None,
url: None,
fetch: true,
stale: false,
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct OutputConfig {
#[serde(default)]
pub deny: Vec<DenyOption>,
#[serde(default)]
pub format: OutputFormat,
pub quiet: bool,
pub show_tree: Option<bool>,
}
impl OutputConfig {
pub fn is_quiet(&self) -> bool {
self.quiet || self.format == OutputFormat::Json || self.format == OutputFormat::Sarif
}
}
#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Serialize, Ord)]
pub enum DenyOption {
#[serde(rename = "warnings")]
Warnings,
#[serde(rename = "unmaintained")]
Unmaintained,
#[serde(rename = "unsound")]
Unsound,
#[serde(rename = "yanked")]
Yanked,
}
impl DenyOption {
pub fn all() -> Vec<Self> {
vec![
DenyOption::Warnings,
DenyOption::Unmaintained,
DenyOption::Unsound,
DenyOption::Yanked,
]
}
pub fn get_warning_kind(self) -> &'static [WarningKind] {
match self {
DenyOption::Warnings => &[
WarningKind::Unmaintained,
WarningKind::Unsound,
WarningKind::Yanked,
],
DenyOption::Unmaintained => &[WarningKind::Unmaintained],
DenyOption::Unsound => &[WarningKind::Unsound],
DenyOption::Yanked => &[WarningKind::Yanked],
}
}
}
impl FromStr for DenyOption {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s {
"warnings" => Ok(DenyOption::Warnings),
"unmaintained" => Ok(DenyOption::Unmaintained),
"unsound" => Ok(DenyOption::Unsound),
"yanked" => Ok(DenyOption::Yanked),
other => Err(Error::new(
ErrorKind::Parse,
format!("invalid deny option: {other}"),
)),
}
}
}
#[derive(Default, Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize, clap::ValueEnum)]
pub enum OutputFormat {
#[serde(rename = "json")]
Json,
#[serde(rename = "sarif")]
Sarif,
#[serde(rename = "terminal")]
#[default]
Terminal,
}
impl FromStr for OutputFormat {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
match s {
"json" => Ok(OutputFormat::Json),
"sarif" => Ok(OutputFormat::Sarif),
"terminal" => Ok(OutputFormat::Terminal),
other => Err(Error::new(
ErrorKind::Parse,
format!("invalid output format: {other}"),
)),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum FilterList<T> {
Single(T),
Many(Vec<T>),
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TargetConfig {
pub arch: Option<FilterList<Arch>>,
pub os: Option<FilterList<OS>>,
}
impl TargetConfig {
pub fn arch(&self) -> Vec<Arch> {
match &self.arch {
Some(FilterList::Single(single)) => vec![*single],
Some(FilterList::Many(many)) => many.clone(),
None => vec![],
}
}
pub fn os(&self) -> Vec<OS> {
match &self.os {
Some(FilterList::Single(single)) => vec![*single],
Some(FilterList::Many(many)) => many.clone(),
None => vec![],
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct YankedConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_true")]
pub update_index: bool,
}
impl Default for YankedConfig {
fn default() -> Self {
Self {
enabled: true,
update_index: true,
}
}
}
fn default_true() -> bool {
true
}