use crate::ports::outbound::EnrichedPackage;
use crate::sbom_generation::domain::license_policy::LicenseComplianceResult;
use crate::sbom_generation::domain::services::VulnerabilityCheckResult;
use crate::sbom_generation::domain::vulnerability::PackageVulnerabilities;
use crate::sbom_generation::domain::{DependencyGraph, SbomMetadata, UpgradeRecommendation};
use crate::shared::error::SbomError;
#[derive(Debug, Clone)]
pub struct SbomResponse {
pub enriched_packages: Vec<EnrichedPackage>,
pub dependency_graph: Option<DependencyGraph>,
pub metadata: SbomMetadata,
#[allow(dead_code)]
pub vulnerability_report: Option<Vec<PackageVulnerabilities>>,
pub has_vulnerabilities_above_threshold: bool,
pub vulnerability_check_result: Option<VulnerabilityCheckResult>,
pub license_compliance_result: Option<LicenseComplianceResult>,
pub has_license_violations: bool,
#[allow(dead_code)] pub upgrade_recommendations: Option<Vec<UpgradeRecommendation>>,
}
impl SbomResponse {
pub fn builder() -> SbomResponseBuilder {
SbomResponseBuilder::new()
}
}
pub struct SbomResponseBuilder {
enriched_packages: Vec<EnrichedPackage>,
dependency_graph: Option<DependencyGraph>,
metadata: Option<SbomMetadata>,
vulnerability_report: Option<Vec<PackageVulnerabilities>>,
has_vulnerabilities_above_threshold: bool,
vulnerability_check_result: Option<VulnerabilityCheckResult>,
license_compliance_result: Option<LicenseComplianceResult>,
has_license_violations: bool,
upgrade_recommendations: Option<Vec<UpgradeRecommendation>>,
}
impl SbomResponseBuilder {
pub fn new() -> Self {
Self {
enriched_packages: Vec::new(),
dependency_graph: None,
metadata: None,
vulnerability_report: None,
has_vulnerabilities_above_threshold: false,
vulnerability_check_result: None,
license_compliance_result: None,
has_license_violations: false,
upgrade_recommendations: None,
}
}
pub fn enriched_packages(mut self, packages: Vec<EnrichedPackage>) -> Self {
self.enriched_packages = packages;
self
}
#[cfg(test)]
pub fn add_enriched_package(mut self, package: EnrichedPackage) -> Self {
self.enriched_packages.push(package);
self
}
pub fn dependency_graph(mut self, graph: DependencyGraph) -> Self {
self.dependency_graph = Some(graph);
self
}
pub fn metadata(mut self, metadata: SbomMetadata) -> Self {
self.metadata = Some(metadata);
self
}
pub fn vulnerability_report(mut self, report: Vec<PackageVulnerabilities>) -> Self {
self.vulnerability_report = Some(report);
self
}
pub fn has_vulnerabilities_above_threshold(mut self, value: bool) -> Self {
self.has_vulnerabilities_above_threshold = value;
self
}
pub fn vulnerability_check_result(mut self, result: VulnerabilityCheckResult) -> Self {
self.vulnerability_check_result = Some(result);
self
}
pub fn license_compliance_result(mut self, result: LicenseComplianceResult) -> Self {
self.license_compliance_result = Some(result);
self
}
pub fn has_license_violations(mut self, value: bool) -> Self {
self.has_license_violations = value;
self
}
#[allow(dead_code)] pub fn upgrade_recommendations(mut self, recommendations: Vec<UpgradeRecommendation>) -> Self {
self.upgrade_recommendations = Some(recommendations);
self
}
pub fn build(self) -> Result<SbomResponse, SbomError> {
let metadata = self.metadata.ok_or_else(|| SbomError::Validation {
message: "metadata is required".into(),
})?;
Ok(SbomResponse {
enriched_packages: self.enriched_packages,
dependency_graph: self.dependency_graph,
metadata,
vulnerability_report: self.vulnerability_report,
has_vulnerabilities_above_threshold: self.has_vulnerabilities_above_threshold,
vulnerability_check_result: self.vulnerability_check_result,
license_compliance_result: self.license_compliance_result,
has_license_violations: self.has_license_violations,
upgrade_recommendations: self.upgrade_recommendations,
})
}
}
impl Default for SbomResponseBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sbom_generation::domain::Package;
use crate::sbom_generation::services::SbomGenerator;
fn create_test_enriched_package(name: &str, version: &str) -> EnrichedPackage {
let package = Package::new(name.to_string(), version.to_string()).unwrap();
EnrichedPackage::new(package, Some("MIT".to_string()), None)
}
#[test]
fn test_builder_with_metadata_only() {
let metadata = SbomGenerator::generate_default_metadata();
let response = SbomResponse::builder()
.metadata(metadata)
.build()
.expect("should build with metadata only");
assert!(response.enriched_packages.is_empty());
assert!(response.dependency_graph.is_none());
assert!(response.vulnerability_report.is_none());
assert!(!response.has_vulnerabilities_above_threshold);
assert!(response.vulnerability_check_result.is_none());
assert!(response.license_compliance_result.is_none());
assert!(!response.has_license_violations);
}
#[test]
fn test_builder_fails_without_metadata() {
let result = SbomResponse::builder().build();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("metadata is required"));
}
#[test]
fn test_builder_with_enriched_packages() {
let response = SbomResponse::builder()
.add_enriched_package(create_test_enriched_package("requests", "2.31.0"))
.add_enriched_package(create_test_enriched_package("certifi", "2024.2.2"))
.metadata(SbomGenerator::generate_default_metadata())
.build()
.expect("should build with packages");
assert_eq!(response.enriched_packages.len(), 2);
}
#[test]
fn test_builder_enriched_packages_bulk() {
let packages = vec![
create_test_enriched_package("pkg1", "1.0.0"),
create_test_enriched_package("pkg2", "2.0.0"),
];
let response = SbomResponse::builder()
.enriched_packages(packages)
.metadata(SbomGenerator::generate_default_metadata())
.build()
.expect("should build with bulk packages");
assert_eq!(response.enriched_packages.len(), 2);
}
#[test]
fn test_builder_with_vulnerability_flags() {
let response = SbomResponse::builder()
.metadata(SbomGenerator::generate_default_metadata())
.has_vulnerabilities_above_threshold(true)
.has_license_violations(true)
.build()
.expect("should build with flags");
assert!(response.has_vulnerabilities_above_threshold);
assert!(response.has_license_violations);
}
#[test]
fn test_builder_default() {
let builder = SbomResponseBuilder::default();
let result = builder
.metadata(SbomGenerator::generate_default_metadata())
.build();
assert!(result.is_ok());
}
}