aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/advisory
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-08 04:22:49 +0900
committernsfisis <nsfisis@gmail.com>2026-06-08 04:23:11 +0900
commit632c9793927a30c4bca3c91888e66b369d732dfe (patch)
tree5bafb9d7fa2523d5a2f8d70ff59c3173e962e389 /crates/shirabe/src/advisory
parent243450fee9853c2fea66ae0642dabb8db5227d6f (diff)
downloadphp-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.rs3
-rw-r--r--crates/shirabe/src/advisory/auditor.rs68
-rw-r--r--crates/shirabe/src/advisory/security_advisory.rs9
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>,