From 212506c364b2342dd9e5fa789e8cff38835dfe52 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 10 May 2026 03:03:34 +0900 Subject: refactor(advisory): split advisory.rs into focused submodules Move AuditConfig, AuditFormat/AbandonedHandling enums, and stub types (SecurityAdvisory, PartialSecurityAdvisory, IgnoredSecurityAdvisory) into their own files under advisory/. Public API is preserved via wildcard re-exports. Co-Authored-By: Claude Sonnet 4.6 --- crates/mozart-core/src/advisory.rs | 334 ++----------------------------------- 1 file changed, 11 insertions(+), 323 deletions(-) (limited to 'crates/mozart-core/src/advisory.rs') diff --git a/crates/mozart-core/src/advisory.rs b/crates/mozart-core/src/advisory.rs index f20405e..bcfbe93 100644 --- a/crates/mozart-core/src/advisory.rs +++ b/crates/mozart-core/src/advisory.rs @@ -1,323 +1,11 @@ -use indexmap::IndexMap; - -use crate::config::Config; - -pub const FORMAT_TABLE: &str = "table"; -pub const FORMAT_PLAIN: &str = "plain"; -pub const FORMAT_JSON: &str = "json"; -pub const FORMAT_SUMMARY: &str = "summary"; -pub const FORMATS: [&str; 4] = [FORMAT_TABLE, FORMAT_PLAIN, FORMAT_JSON, FORMAT_SUMMARY]; - -pub const ABANDONED_IGNORE: &str = "ignore"; -pub const ABANDONED_REPORT: &str = "report"; -pub const ABANDONED_FAIL: &str = "fail"; -pub const ABANDONEDS: [&str; 3] = [ABANDONED_IGNORE, ABANDONED_REPORT, ABANDONED_FAIL]; - -/// Mirrors `Auditor::FORMAT_*` constants. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum AuditFormat { - #[default] - Table, - Plain, - Json, - Summary, -} - -impl AuditFormat { - pub fn as_str(self) -> &'static str { - match self { - Self::Table => FORMAT_TABLE, - Self::Plain => FORMAT_PLAIN, - Self::Json => FORMAT_JSON, - Self::Summary => FORMAT_SUMMARY, - } - } -} - -impl std::str::FromStr for AuditFormat { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - FORMAT_TABLE => Ok(Self::Table), - FORMAT_PLAIN => Ok(Self::Plain), - FORMAT_JSON => Ok(Self::Json), - FORMAT_SUMMARY => Ok(Self::Summary), - _ => Err(()), - } - } -} - -/// Mirrors `Auditor::ABANDONED_*` constants. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum AbandonedHandling { - Ignore, - Report, - #[default] - Fail, -} - -impl AbandonedHandling { - pub fn as_str(self) -> &'static str { - match self { - Self::Ignore => ABANDONED_IGNORE, - Self::Report => ABANDONED_REPORT, - Self::Fail => ABANDONED_FAIL, - } - } -} - -impl std::str::FromStr for AbandonedHandling { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - ABANDONED_IGNORE => Ok(Self::Ignore), - ABANDONED_REPORT => Ok(Self::Report), - ABANDONED_FAIL => Ok(Self::Fail), - _ => Err(()), - } - } -} - -/// Mirrors `Composer\Advisory\AuditConfig`. -#[derive(Debug, Clone)] -pub struct AuditConfig { - pub audit: bool, - pub audit_format: AuditFormat, - pub audit_abandoned: AbandonedHandling, - pub block_insecure: bool, - pub block_abandoned: bool, - pub ignore_unreachable: bool, - pub ignore_list_for_audit: IndexMap>, - pub ignore_list_for_blocking: IndexMap>, - pub ignore_severity_for_audit: IndexMap>, - pub ignore_severity_for_blocking: IndexMap>, - pub ignore_abandoned_for_audit: IndexMap>, - pub ignore_abandoned_for_blocking: IndexMap>, -} - -struct ParsedIgnore { - audit: IndexMap>, - block: IndexMap>, -} - -/// Mirrors `AuditConfig::parseIgnoreWithApply()`. -/// -/// Supports these JSON shapes: -/// - `["CVE-1"]` — simple list, apply=all, reason=null -/// - `{"CVE-1": "reason"}` — with reason, apply=all -/// - `{"CVE-1": null}` — null reason, apply=all -/// - `{"CVE-1": {"apply": "audit|block|all", "reason": "..."}}` — detailed -fn parse_ignore_with_apply(config: &serde_json::Value) -> ParsedIgnore { - let mut for_audit: IndexMap> = IndexMap::new(); - let mut for_block: IndexMap> = IndexMap::new(); - - match config { - serde_json::Value::Array(arr) => { - for item in arr { - if let Some(s) = item.as_str() { - for_audit.insert(s.to_string(), None); - for_block.insert(s.to_string(), None); - } - } - } - serde_json::Value::Object(obj) => { - for (key, value) in obj { - let (apply, reason) = match value { - serde_json::Value::String(r) => ("all", Some(r.clone())), - serde_json::Value::Null => ("all", None), - serde_json::Value::Object(detail) => { - let apply = detail - .get("apply") - .and_then(|v| v.as_str()) - .unwrap_or("all"); - if !matches!(apply, "audit" | "block" | "all") { - continue; - } - let reason = detail - .get("reason") - .and_then(|v| v.as_str()) - .map(|s| s.to_string()); - (apply, reason) - } - _ => continue, - }; - - if apply == "audit" || apply == "all" { - for_audit.insert(key.clone(), reason.clone()); - } - if apply == "block" || apply == "all" { - for_block.insert(key.clone(), reason); - } - } - } - _ => {} - } - - ParsedIgnore { - audit: for_audit, - block: for_block, - } -} - -impl AuditConfig { - /// Mirrors `AuditConfig::fromConfig()`. - pub fn from_config(config: &Config, audit: bool, audit_format: AuditFormat) -> Self { - let empty_arr = serde_json::Value::Array(vec![]); - let audit_val = config - .get("audit") - .unwrap_or_else(|| serde_json::Value::Object(Default::default())); - - let ignore_list_val = audit_val - .get("ignore") - .cloned() - .unwrap_or_else(|| empty_arr.clone()); - let ignore_list_parsed = parse_ignore_with_apply(&ignore_list_val); - - let ignore_abandoned_val = audit_val - .get("ignore-abandoned") - .cloned() - .unwrap_or_else(|| empty_arr.clone()); - let ignore_abandoned_parsed = parse_ignore_with_apply(&ignore_abandoned_val); - - let ignore_severity_val = audit_val - .get("ignore-severity") - .cloned() - .unwrap_or_else(|| empty_arr.clone()); - let ignore_severity_parsed = parse_ignore_with_apply(&ignore_severity_val); - - let audit_abandoned = audit_val - .get("abandoned") - .and_then(|v| v.as_str()) - .and_then(|s| s.parse::().ok()) - .unwrap_or_default(); - - let block_insecure = audit_val - .get("block-insecure") - .and_then(|v| v.as_bool()) - .unwrap_or(true); - - let block_abandoned = audit_val - .get("block-abandoned") - .and_then(|v| v.as_bool()) - .unwrap_or(false); - - let ignore_unreachable = audit_val - .get("ignore-unreachable") - .and_then(|v| v.as_bool()) - .unwrap_or(false); - - Self { - audit, - audit_format, - audit_abandoned, - block_insecure, - block_abandoned, - ignore_unreachable, - ignore_list_for_audit: ignore_list_parsed.audit, - ignore_list_for_blocking: ignore_list_parsed.block, - ignore_severity_for_audit: ignore_severity_parsed.audit, - ignore_severity_for_blocking: ignore_severity_parsed.block, - ignore_abandoned_for_audit: ignore_abandoned_parsed.audit, - ignore_abandoned_for_blocking: ignore_abandoned_parsed.block, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_ignore_simple_array() { - let val = serde_json::json!(["CVE-2024-1234", "PKSA-0001"]); - let parsed = parse_ignore_with_apply(&val); - assert_eq!(parsed.audit.len(), 2); - assert_eq!(parsed.block.len(), 2); - assert_eq!(parsed.audit.get("CVE-2024-1234"), Some(&None)); - } - - #[test] - fn test_parse_ignore_object_with_reasons() { - let val = serde_json::json!({ - "CVE-2024-1234": "manually patched", - "PKSA-0001": null - }); - let parsed = parse_ignore_with_apply(&val); - assert_eq!( - parsed.audit.get("CVE-2024-1234"), - Some(&Some("manually patched".to_string())) - ); - assert_eq!(parsed.audit.get("PKSA-0001"), Some(&None)); - } - - #[test] - fn test_parse_ignore_detailed_apply_audit_only() { - let val = serde_json::json!({ - "CVE-2024-1234": { "apply": "audit", "reason": "test" } - }); - let parsed = parse_ignore_with_apply(&val); - assert_eq!(parsed.audit.len(), 1); - assert_eq!(parsed.block.len(), 0); - } - - #[test] - fn test_parse_ignore_detailed_apply_block_only() { - let val = serde_json::json!({ - "CVE-2024-1234": { "apply": "block", "reason": "test" } - }); - let parsed = parse_ignore_with_apply(&val); - assert_eq!(parsed.audit.len(), 0); - assert_eq!(parsed.block.len(), 1); - } - - #[test] - fn test_parse_ignore_detailed_apply_all() { - let val = serde_json::json!({ - "CVE-2024-1234": { "apply": "all", "reason": "test" } - }); - let parsed = parse_ignore_with_apply(&val); - assert_eq!(parsed.audit.len(), 1); - assert_eq!(parsed.block.len(), 1); - } - - #[test] - fn test_audit_config_defaults() { - let config = Config::default(); - let audit_config = AuditConfig::from_config(&config, true, AuditFormat::Table); - assert!(audit_config.audit); - assert_eq!(audit_config.audit_format, AuditFormat::Table); - assert_eq!(audit_config.audit_abandoned, AbandonedHandling::Fail); - assert!(audit_config.block_insecure); - assert!(!audit_config.block_abandoned); - assert!(!audit_config.ignore_unreachable); - assert!(audit_config.ignore_list_for_audit.is_empty()); - assert!(audit_config.ignore_severity_for_audit.is_empty()); - } - - #[test] - fn test_audit_config_from_config_with_audit_section() { - use std::collections::BTreeMap; - let mut config = Config::default(); - config - .merge(&BTreeMap::from([( - "audit".to_string(), - serde_json::json!({ - "ignore": ["CVE-2024-1234"], - "ignore-severity": {"low": "low severity not critical"}, - "abandoned": "report", - "block-insecure": false, - "ignore-unreachable": true - }), - )])) - .unwrap(); - - let audit_config = AuditConfig::from_config(&config, true, AuditFormat::Summary); - assert_eq!(audit_config.audit_abandoned, AbandonedHandling::Report); - assert!(!audit_config.block_insecure); - assert!(audit_config.ignore_unreachable); - assert_eq!(audit_config.ignore_list_for_audit.len(), 1); - assert_eq!(audit_config.ignore_severity_for_audit.len(), 1); - } -} +pub mod audit_config; +pub mod auditor; +pub mod ignored_security_advisory; +pub mod partial_security_advisory; +pub mod security_advisory; + +pub use audit_config::*; +pub use auditor::*; +pub use ignored_security_advisory::*; +pub use partial_security_advisory::*; +pub use security_advisory::*; -- cgit v1.3.1