//! ref: composer/src/Composer/Advisory/PartialSecurityAdvisory.php use crate::advisory::SecurityAdvisory; use crate::repository::PartialOrSecurityAdvisory; use anyhow::Result; use chrono::{DateTime, TimeZone, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; use shirabe_php_shim::{PhpMixed, UnexpectedValueException}; use shirabe_semver::constraint::Constraint; use shirabe_semver::constraint::ConstraintInterface; use shirabe_semver::version_parser::VersionParser; fn serialize_constraint( c: &Box, serializer: S, ) -> Result { serializer.serialize_str(&c.get_pretty_string()) } #[derive(Debug, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct PartialSecurityAdvisory { pub advisory_id: String, pub package_name: String, #[serde(serialize_with = "serialize_constraint")] pub affected_versions: Box, } impl PartialSecurityAdvisory { pub fn create( package_name: &str, data: &IndexMap, parser: &VersionParser, ) -> Result { let affected_versions_str = data["affectedVersions"].as_string().unwrap_or(""); let constraint: Box = match parser.parse_constraints(affected_versions_str) { Ok(c) => c, Err(_) => { let affected_version = Preg::replace(r"(^[>=<^~]*[\d.]+).*", "$1", affected_versions_str); match parser.parse_constraints(affected_version.as_deref().unwrap_or("")) { Ok(c) => c, Err(_) => Box::new(Constraint::new( "==".to_string(), "0.0.0-invalid-version".to_string(), )), } } }; let has_full_data = data.contains_key("title") && data.contains_key("sources") && data.contains_key("reportedAt"); if has_full_data { let reported_at: DateTime = Utc .datetime_from_str( data["reportedAt"].as_string().unwrap_or(""), "%Y-%m-%dT%H:%M:%S+00:00", ) .unwrap_or_default(); let advisory = SecurityAdvisory::new( package_name.to_string(), data["advisoryId"].as_string().unwrap_or("").to_string(), constraint, data["title"].as_string().unwrap_or("").to_string(), // TODO(phase-b): parse PhpMixed sources array into Vec> todo!(), reported_at, data.get("cve") .and_then(|v| v.as_string()) .map(|s| s.to_string()), data.get("link") .and_then(|v| v.as_string()) .map(|s| s.to_string()), data.get("severity") .and_then(|v| v.as_string()) .map(|s| s.to_string()), ); return Ok(PartialOrSecurityAdvisory::Full(advisory)); } Ok(PartialOrSecurityAdvisory::Partial(Self { advisory_id: data["advisoryId"].as_string().unwrap_or("").to_string(), package_name: package_name.to_string(), affected_versions: constraint, })) } pub fn new( package_name: String, advisory_id: String, affected_versions: Box, ) -> Self { Self { advisory_id, package_name, affected_versions, } } }