From 632c9793927a30c4bca3c91888e66b369d732dfe Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 8 Jun 2026 04:22:49 +0900 Subject: feat(phase-c): resolve PhpMixed-conversion phase-b TODOs Implement the foundational PhpMixed conversion infrastructure (From, order-sensitive PartialEq matching PHP ===) and resolve the category-G phase-b TODOs that depend on it: - Fix VCS driver cache paths that discarded parsed JSON or diverged on null caches (svn/forgejo/gitlab/git-bitbucket/github). - Wire up real conversions previously stubbed or dropped: suggests platform config, audit ignore-severities, composer_repository search and ProviderInfo, class_loader prefix/classmap merges, locker lock diff comparison, advisory JSON serialization, SPDX license fields. - Make GenericRule take a typed ReasonData; populate RULE_ROOT_REQUIRE with the constraint and convert PhpMixed at the call sites. --- .../shirabe/src/advisory/any_security_advisory.rs | 3 +- crates/shirabe/src/advisory/auditor.rs | 68 ++++++++++------------ crates/shirabe/src/advisory/security_advisory.rs | 9 +++ 3 files changed, 41 insertions(+), 39 deletions(-) (limited to 'crates/shirabe/src/advisory') diff --git a/crates/shirabe/src/advisory/any_security_advisory.rs b/crates/shirabe/src/advisory/any_security_advisory.rs index c48df33..b6df764 100644 --- a/crates/shirabe/src/advisory/any_security_advisory.rs +++ b/crates/shirabe/src/advisory/any_security_advisory.rs @@ -3,7 +3,8 @@ use crate::advisory::PartialSecurityAdvisory; use crate::advisory::SecurityAdvisory; use shirabe_semver::constraint::AnyConstraint; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, serde::Serialize)] +#[serde(untagged)] pub enum AnySecurityAdvisory { Partial(PartialSecurityAdvisory), Full(SecurityAdvisory), diff --git a/crates/shirabe/src/advisory/auditor.rs b/crates/shirabe/src/advisory/auditor.rs index 2fc93f3..179278a 100644 --- a/crates/shirabe/src/advisory/auditor.rs +++ b/crates/shirabe/src/advisory/auditor.rs @@ -22,6 +22,20 @@ use crate::package::base_package::BasePackage; use crate::repository::RepositorySet; use crate::util::PackageInfo; +/// Shape of the `--format=json` audit output. +#[derive(serde::Serialize)] +struct AuditJsonReport<'a> { + advisories: &'a IndexMap>, + #[serde(rename = "ignored-advisories", skip_serializing_if = "Option::is_none")] + ignored_advisories: Option<&'a IndexMap>>, + #[serde( + rename = "unreachable-repositories", + skip_serializing_if = "Option::is_none" + )] + unreachable_repositories: Option<&'a Vec>, + abandoned: IndexMap>, +} + /// @internal #[derive(Debug)] pub struct Auditor; @@ -122,24 +136,7 @@ impl Auditor { self.calculate_bitmask(0 < affected_packages_count, 0 < abandoned_count); if Self::FORMAT_JSON == format { - let mut json: IndexMap = IndexMap::new(); - // TODO(phase-b): serialize advisories / ignored_advisories into PhpMixed - json.insert("advisories".to_string(), PhpMixed::Null); - if !ignored_advisories.is_empty() { - json.insert("ignored-advisories".to_string(), PhpMixed::Null); - } - if !unreachable_repos.is_empty() { - json.insert( - "unreachable-repositories".to_string(), - PhpMixed::List( - unreachable_repos - .iter() - .map(|r| Box::new(PhpMixed::String(r.clone()))) - .collect(), - ), - ); - } - let abandoned_map = array_reduce( + let abandoned = array_reduce( &abandoned_packages, |mut carry: IndexMap>, package: &CompletePackageInterfaceHandle| { @@ -148,27 +145,22 @@ impl Auditor { }, IndexMap::new(), ); - json.insert( - "abandoned".to_string(), - PhpMixed::Array( - abandoned_map - .into_iter() - .map(|(k, v)| { - ( - k, - Box::new(match v { - Some(s) => PhpMixed::String(s), - None => PhpMixed::Null, - }), - ) - }) - .collect(), - ), - ); + let report = AuditJsonReport { + advisories: &advisories, + ignored_advisories: if ignored_advisories.is_empty() { + None + } else { + Some(&ignored_advisories) + }, + unreachable_repositories: if unreachable_repos.is_empty() { + None + } else { + Some(&unreachable_repos) + }, + abandoned, + }; - io.write(&JsonFile::encode(&PhpMixed::Array( - json.into_iter().map(|(k, v)| (k, Box::new(v))).collect(), - ))); + io.write(&JsonFile::encode(&report)); return Ok(audit_bitmask); } diff --git a/crates/shirabe/src/advisory/security_advisory.rs b/crates/shirabe/src/advisory/security_advisory.rs index 5cb5c59..2c5a309 100644 --- a/crates/shirabe/src/advisory/security_advisory.rs +++ b/crates/shirabe/src/advisory/security_advisory.rs @@ -7,6 +7,14 @@ use shirabe_semver::constraint::AnyConstraint; use crate::advisory::IgnoredSecurityAdvisory; use crate::advisory::PartialSecurityAdvisory; +/// Matches PHP's `format(DATE_RFC3339)`, e.g. "2020-01-01T00:00:00+00:00". +fn serialize_date_rfc3339( + dt: &DateTime, + serializer: S, +) -> Result { + serializer.serialize_str(&dt.format("%Y-%m-%dT%H:%M:%S%:z").to_string()) +} + #[derive(Debug, Clone, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct SecurityAdvisory { @@ -15,6 +23,7 @@ pub struct SecurityAdvisory { pub title: String, pub cve: Option, pub link: Option, + #[serde(serialize_with = "serialize_date_rfc3339")] pub reported_at: DateTime, pub sources: Vec>, pub severity: Option, -- cgit v1.3.1