use serde::{Deserialize, Serialize};
use super::{Advisory, AuditStatus, Result, Severity, SupplyChainError};
const FIELD_TYPE: &str = "type";
const FIELD_FIELDS: &str = "fields";
const FIELD_SEVERITY: &str = "severity";
const FIELD_CODE: &str = "code";
const FIELD_LABELS: &str = "labels";
const FIELD_SPAN: &str = "span";
const FIELD_CRATE: &str = "crate";
const FIELD_NAME: &str = "name";
const FIELD_VERSION: &str = "version";
const FIELD_MESSAGE: &str = "message";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DependencyAudit {
pub crate_name: String,
pub version: String,
pub advisories: Vec<Advisory>,
pub license: String,
pub audit_status: AuditStatus,
}
impl DependencyAudit {
pub fn clean(
crate_name: impl Into<String>,
version: impl Into<String>,
license: impl Into<String>,
) -> Self {
Self {
crate_name: crate_name.into(),
version: version.into(),
advisories: Vec::new(),
license: license.into(),
audit_status: AuditStatus::Clean,
}
}
pub fn vulnerable(
crate_name: impl Into<String>,
version: impl Into<String>,
license: impl Into<String>,
advisories: Vec<Advisory>,
) -> Self {
Self {
crate_name: crate_name.into(),
version: version.into(),
advisories,
license: license.into(),
audit_status: AuditStatus::Vulnerable,
}
}
pub fn from_cargo_deny_output(json: &str) -> Result<Vec<Self>> {
let mut audits = Vec::new();
for line in json.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let value: serde_json::Value = serde_json::from_str(line)
.map_err(|e| SupplyChainError::ParseError(e.to_string()))?;
if value.get(FIELD_TYPE).and_then(|t| t.as_str()) != Some("diagnostic") {
continue;
}
let fields = match value.get(FIELD_FIELDS) {
Some(f) => f,
None => continue,
};
let severity_str =
fields.get(FIELD_SEVERITY).and_then(|s| s.as_str()).unwrap_or("none");
let is_vulnerability = severity_str == "error"
&& fields
.get(FIELD_CODE)
.and_then(|c| c.as_str())
.is_some_and(|c| c.starts_with('A'));
if !is_vulnerability {
continue;
}
if let Some(labels) = fields.get(FIELD_LABELS).and_then(|l| l.as_array()) {
for label in labels {
if let Some(span) = label.get(FIELD_SPAN) {
if let Some(krate) = span.get(FIELD_CRATE) {
let crate_name = krate
.get(FIELD_NAME)
.and_then(|n| n.as_str())
.unwrap_or("unknown")
.to_string();
let version = krate
.get(FIELD_VERSION)
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let message = fields
.get(FIELD_MESSAGE)
.and_then(|m| m.as_str())
.unwrap_or("Unknown vulnerability")
.to_string();
let code = fields
.get(FIELD_CODE)
.and_then(|c| c.as_str())
.unwrap_or("UNKNOWN")
.to_string();
let advisory = Advisory::new(code, Severity::High, message);
audits.push(Self::vulnerable(
crate_name,
version,
"unknown",
vec![advisory],
));
}
}
}
}
}
Ok(audits)
}
pub fn is_vulnerable(&self) -> bool {
self.audit_status == AuditStatus::Vulnerable
}
pub fn max_severity(&self) -> Severity {
self.advisories.iter().map(|a| a.severity).max().unwrap_or(Severity::None)
}
}