diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-08 04:22:49 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-08 04:23:11 +0900 |
| commit | 632c9793927a30c4bca3c91888e66b369d732dfe (patch) | |
| tree | 5bafb9d7fa2523d5a2f8d70ff59c3173e962e389 /crates/shirabe/src/advisory | |
| parent | 243450fee9853c2fea66ae0642dabb8db5227d6f (diff) | |
| download | php-shirabe-632c9793927a30c4bca3c91888e66b369d732dfe.tar.gz php-shirabe-632c9793927a30c4bca3c91888e66b369d732dfe.tar.zst php-shirabe-632c9793927a30c4bca3c91888e66b369d732dfe.zip | |
feat(phase-c): resolve PhpMixed-conversion phase-b TODOs
Implement the foundational PhpMixed conversion infrastructure
(From<bool|i64|f64|String>, 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.
Diffstat (limited to 'crates/shirabe/src/advisory')
| -rw-r--r-- | crates/shirabe/src/advisory/any_security_advisory.rs | 3 | ||||
| -rw-r--r-- | crates/shirabe/src/advisory/auditor.rs | 68 | ||||
| -rw-r--r-- | crates/shirabe/src/advisory/security_advisory.rs | 9 |
3 files changed, 41 insertions, 39 deletions
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<String, Vec<AnySecurityAdvisory>>, + #[serde(rename = "ignored-advisories", skip_serializing_if = "Option::is_none")] + ignored_advisories: Option<&'a IndexMap<String, Vec<AnySecurityAdvisory>>>, + #[serde( + rename = "unreachable-repositories", + skip_serializing_if = "Option::is_none" + )] + unreachable_repositories: Option<&'a Vec<String>>, + abandoned: IndexMap<String, Option<String>>, +} + /// @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<String, PhpMixed> = 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<String, Option<String>>, 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<S: serde::Serializer>( + dt: &DateTime<Utc>, + serializer: S, +) -> Result<S::Ok, S::Error> { + 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<String>, pub link: Option<String>, + #[serde(serialize_with = "serialize_date_rfc3339")] pub reported_at: DateTime<Utc>, pub sources: Vec<IndexMap<String, String>>, pub severity: Option<String>, |
