From 62e9625564b030b40fbb5afc06202d2c2615e128 Mon Sep 17 00:00:00 2001 From: "bxf12315@gmail.com" Date: Fri, 12 Sep 2025 18:43:41 +0800 Subject: [PATCH 1/4] TC-2826 Packages List API - Add licenses array --- entity/src/sbom_package_license.rs | 18 +++++ modules/fundamental/src/common/mod.rs | 10 +++ modules/fundamental/src/common/service.rs | 37 +++++++++- .../fundamental/src/license/service/mod.rs | 2 +- .../fundamental/src/purl/endpoints/test.rs | 71 +++++++++++++++++- .../src/purl/model/details/purl.rs | 72 +++++++++++++++++-- modules/fundamental/src/sbom/endpoints/mod.rs | 2 +- modules/fundamental/src/sbom/model/mod.rs | 12 +--- modules/fundamental/src/sbom/service/sbom.rs | 11 +-- openapi.yaml | 23 +++--- 10 files changed, 225 insertions(+), 33 deletions(-) diff --git a/entity/src/sbom_package_license.rs b/entity/src/sbom_package_license.rs index bd9e79dfb..d285b1c3e 100644 --- a/entity/src/sbom_package_license.rs +++ b/entity/src/sbom_package_license.rs @@ -68,3 +68,21 @@ impl Related for Entity { } impl ActiveModelBehavior for ActiveModel {} + +impl LicenseCategory { + /// Returns the string representation of the license category + /// + /// # Examples + /// ``` + /// use trustify_entity::sbom_package_license::LicenseCategory; + /// + /// assert_eq!(LicenseCategory::Declared.show(), "Declared"); + /// assert_eq!(LicenseCategory::Concluded.show(), "Concluded"); + /// ``` + pub fn show(&self) -> &'static str { + match self { + LicenseCategory::Declared => "Declared", + LicenseCategory::Concluded => "Concluded", + } + } +} diff --git a/modules/fundamental/src/common/mod.rs b/modules/fundamental/src/common/mod.rs index 1f278a4d5..1fc086fa4 100644 --- a/modules/fundamental/src/common/mod.rs +++ b/modules/fundamental/src/common/mod.rs @@ -1 +1,11 @@ +use sea_orm::FromQueryResult; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + pub mod service; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema, FromQueryResult)] +pub struct LicenseRefMapping { + pub license_id: String, + pub license_name: String, +} diff --git a/modules/fundamental/src/common/service.rs b/modules/fundamental/src/common/service.rs index 91a6016d6..43453174d 100644 --- a/modules/fundamental/src/common/service.rs +++ b/modules/fundamental/src/common/service.rs @@ -1,5 +1,7 @@ -use crate::{Error, source_document::model::SourceDocument}; +use crate::{Error, common::LicenseRefMapping, source_document::model::SourceDocument}; use sea_orm::{ConnectionTrait, DbBackend, FromQueryResult, PaginatorTrait, Statement}; +use spdx_expression; +use std::collections::BTreeMap; use trustify_module_storage::service::{StorageBackend, StorageKey, dispatch::DispatchBackend}; #[derive(Copy, Clone, Eq, PartialEq)] @@ -85,6 +87,39 @@ impl DocumentDelete for &DispatchBackend { } } +/// Extract LicenseRef mappings from SPDX license expressions +/// +/// This function parses SPDX license expressions and extracts LicenseRef mappings, +/// which are then added to the provided `licenses_ref_mapping` vector. +/// +/// # Arguments +/// * `license_name` - The SPDX license expression to parse +/// * `licensing_infos` - A BTreeMap containing license ID to license name mappings +/// * `licenses_ref_mapping` - A mutable vector where LicenseRef mappings will be added +pub fn extract_license_ref_mappings( + license_name: &str, + licensing_infos: &BTreeMap, + licenses_ref_mapping: &mut Vec, +) { + if let Ok(parsed) = spdx_expression::SpdxExpression::parse(license_name) { + parsed + .licenses() + .into_iter() + .filter(|license| license.license_ref) + .for_each(|license| { + let license_id = license.to_string(); + let license_name = licensing_infos + .get(&license_id) + .cloned() + .unwrap_or_default(); + licenses_ref_mapping.push(LicenseRefMapping { + license_id, + license_name, + }); + }); + } +} + #[cfg(test)] mod test { use super::*; diff --git a/modules/fundamental/src/license/service/mod.rs b/modules/fundamental/src/license/service/mod.rs index db814b4c0..ecc94825e 100644 --- a/modules/fundamental/src/license/service/mod.rs +++ b/modules/fundamental/src/license/service/mod.rs @@ -1,6 +1,6 @@ -use crate::sbom::model::LicenseRefMapping; use crate::{ Error, + common::LicenseRefMapping, license::model::{ SpdxLicenseDetails, SpdxLicenseSummary, sbom_license::{ diff --git a/modules/fundamental/src/purl/endpoints/test.rs b/modules/fundamental/src/purl/endpoints/test.rs index 50545b94a..c643a1262 100644 --- a/modules/fundamental/src/purl/endpoints/test.rs +++ b/modules/fundamental/src/purl/endpoints/test.rs @@ -3,7 +3,7 @@ use crate::purl::model::summary::base_purl::BasePurlSummary; use crate::purl::model::summary::purl::PurlSummary; use crate::test::caller; use actix_web::test::TestRequest; -use serde_json::Value; +use serde_json::{Value, json}; use std::str::FromStr; use test_context::test_context; use test_log::test; @@ -11,7 +11,7 @@ use trustify_common::db::Database; use trustify_common::model::PaginatedResults; use trustify_common::purl::Purl; use trustify_module_ingestor::graph::Graph; -use trustify_test_context::{TrustifyContext, call::CallService}; +use trustify_test_context::{TrustifyContext, call::CallService, subset::ContainsSubset}; use urlencoding::encode; use uuid::Uuid; @@ -243,3 +243,70 @@ async fn purl_filter_queries(ctx: &TrustifyContext) -> Result<(), anyhow::Error> Ok(()) } + +#[test_context(TrustifyContext)] +#[test(actix_web::test)] +async fn test_purl_license_details(ctx: &TrustifyContext) -> Result<(), anyhow::Error> { + let app = caller(ctx).await?; + + ctx.ingest_documents(["spdx/OCP-TOOLS-4.11-RHEL-8.json"]) + .await?; + + let uri = "/api/v2/purl?q=graphite2"; + let request = TestRequest::get().uri(uri).to_request(); + let response: PaginatedResults = app.call_and_read_body_json(request).await; + + assert_eq!(1, response.items.len()); + + let uuid = response.items[0].head.uuid; + + let uri = format!("/api/v2/purl/{uuid}"); + + let request = TestRequest::get().uri(&uri).to_request(); + let response: Value = app.call_and_read_body_json(request).await; + + let expected_result = json!({ + "uuid": "7ff60cd2-d779-586e-b829-cc6d51750450", + "purl": "pkg:rpm/redhat/graphite2@1.3.10-10.el8?arch=ppc64le", + "version": { + "uuid": "57664d22-7f7f-56a0-9c38-9b0dc203b322", + "purl": "pkg:rpm/redhat/graphite2@1.3.10-10.el8", + "version": "1.3.10-10.el8" + }, + "base": { + "uuid": "ba5eb886-34f6-5830-8902-a6182a6a8d7d", + "purl": "pkg:rpm/redhat/graphite2" + }, + "advisories": [], + "licenses": [ + { + "license_name": "(LicenseRef-8 OR LicenseRef-0 OR LicenseRef-MPL) AND (LicenseRef-Netscape OR LicenseRef-0 OR LicenseRef-8)", + "license_type": "Declared" + }, + { + "license_name": "NOASSERTION", + "license_type": "Concluded" + } + ], + "license_ref_mapping": [ + { + "license_id": "LicenseRef-Netscape", + "license_name": "Netscape" + }, + { + "license_id": "LicenseRef-MPL", + "license_name": "MPL" + }, + { + "license_id": "LicenseRef-8", + "license_name": "LGPLv2+" + }, + { + "license_id": "LicenseRef-0", + "license_name": "GPLv2+" + } + ] + }); + assert!(expected_result.contains_subset(response.clone())); + Ok(()) +} diff --git a/modules/fundamental/src/purl/model/details/purl.rs b/modules/fundamental/src/purl/model/details/purl.rs index dc2e0d80f..cf69c7a75 100644 --- a/modules/fundamental/src/purl/model/details/purl.rs +++ b/modules/fundamental/src/purl/model/details/purl.rs @@ -1,14 +1,15 @@ use crate::{ Error, advisory::model::AdvisoryHead, + common::{LicenseRefMapping, service::extract_license_ref_mappings}, purl::model::{BasePurlHead, PurlHead, VersionedPurlHead}, - sbom::model::SbomHead, + sbom::{model::SbomHead, service::SbomService}, vulnerability::model::VulnerabilityHead, }; use sea_orm::{ ColumnTrait, ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Iterable, LoaderTrait, ModelTrait, QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, RelationTrait, - Select, + Select, Statement, }; use sea_query::{Asterisk, ColumnRef, Expr, Func, IntoIden, JoinType, SimpleExpr}; use serde::{Deserialize, Serialize}; @@ -20,6 +21,7 @@ use trustify_common::{ purl::Purl, }; use trustify_cvss::cvss3::{Cvss3Base, score::Score, severity::Severity}; +use trustify_entity::sbom_package_license::LicenseCategory; use trustify_entity::{ advisory, base_purl, cpe, cvss3, license, organization, product, product_status, product_version, product_version_range, purl_status, qualified_purl, sbom, sbom_package, @@ -36,7 +38,32 @@ pub struct PurlDetails { pub version: VersionedPurlHead, pub base: BasePurlHead, pub advisories: Vec, - pub licenses: Vec, + pub licenses: Vec, + pub license_ref_mapping: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema, Default)] +pub struct PurlLicenseInfo { + pub license_name: String, + pub license_type: String, +} + +impl PurlLicenseInfo { + pub fn from_purl_license_result(plr: PurlLicenseResult) -> Self { + PurlLicenseInfo { + license_name: plr.license_name, + license_type: plr.license_type.show().to_string(), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema, FromQueryResult)] +pub struct PurlLicenseResult { + pub sbom_id: Uuid, + pub license_id: Uuid, + pub qualified_package_id: Uuid, + pub license_name: String, + pub license_type: LicenseCategory, } impl PurlDetails { @@ -95,12 +122,49 @@ impl PurlDetails { ) .await?; + let purl_license_results: Vec = + PurlLicenseResult::find_by_statement(Statement::from_sql_and_values( + tx.get_database_backend(), + r#" + SELECT DISTINCT + l.text as license_name, + qp.id as qualified_package_id, + spl.license_type, + sp.sbom_id, + l.id as license_id + FROM qualified_purl qp + JOIN sbom_package_purl_ref sppr ON qp.id = sppr.qualified_purl_id + JOIN sbom_package sp ON sppr.sbom_id = sp.sbom_id AND sppr.node_id = sp.node_id + JOIN sbom_package_license spl ON sp.sbom_id = spl.sbom_id AND sp.node_id = spl.node_id + JOIN license l ON spl.license_id = l.id + WHERE qp.purl = $1 + ORDER BY spl.license_type, sp.sbom_id, l.text + "#, + [qualified_package.clone().purl.into()], + )) + .all(tx) + .await?; + + let mut purl_license_info = Vec::new(); + let mut license_ref_mapping = Vec::new(); + + for plr in purl_license_results { + let licensing_infos = SbomService::get_licensing_infos(tx, plr.sbom_id).await?; + extract_license_ref_mappings( + plr.license_name.as_str(), + &licensing_infos, + &mut license_ref_mapping, + ); + purl_license_info.push(PurlLicenseInfo::from_purl_license_result(plr)); + } + Ok(PurlDetails { head: PurlHead::from_entity(&package, &package_version, qualified_package), version: VersionedPurlHead::from_entity(&package, &package_version), base: BasePurlHead::from_entity(&package), advisories: PurlAdvisory::from_entities(purl_statuses, product_statuses, tx).await?, - licenses: vec![], // Leave it empty for now and wait to add relevant content later. + licenses: purl_license_info, + license_ref_mapping, }) } } diff --git a/modules/fundamental/src/sbom/endpoints/mod.rs b/modules/fundamental/src/sbom/endpoints/mod.rs index 3fb3349ab..c53a8e4a9 100644 --- a/modules/fundamental/src/sbom/endpoints/mod.rs +++ b/modules/fundamental/src/sbom/endpoints/mod.rs @@ -9,7 +9,7 @@ pub use query::*; use crate::sbom::model::LicenseRefMapping; use crate::{ Error, - common::service::delete_doc, + common::{LicenseRefMapping, service::delete_doc}, license::{ get_sanitize_filename, service::{LicenseService, license_export::LicenseExporter}, diff --git a/modules/fundamental/src/sbom/model/mod.rs b/modules/fundamental/src/sbom/model/mod.rs index 8bdffbf5a..e1975eb19 100644 --- a/modules/fundamental/src/sbom/model/mod.rs +++ b/modules/fundamental/src/sbom/model/mod.rs @@ -3,10 +3,10 @@ pub mod raw_sql; use super::service::SbomService; use crate::{ - Error, purl::model::summary::purl::PurlSummary, sbom::service::sbom::LicenseBasicInfo, - source_document::model::SourceDocument, + Error, common::LicenseRefMapping, purl::model::summary::purl::PurlSummary, + sbom::service::sbom::LicenseBasicInfo, source_document::model::SourceDocument, }; -use sea_orm::{ConnectionTrait, FromQueryResult, ModelTrait, PaginatorTrait, prelude::Uuid}; +use sea_orm::{ConnectionTrait, ModelTrait, PaginatorTrait, prelude::Uuid}; use sea_query::FromValueTuple; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; @@ -153,12 +153,6 @@ impl From for LicenseInfo { } } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema, FromQueryResult)] -pub struct LicenseRefMapping { - pub license_id: String, - pub license_name: String, -} - #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum SbomPackageReference<'a> { Internal(&'a str), diff --git a/modules/fundamental/src/sbom/service/sbom.rs b/modules/fundamental/src/sbom/service/sbom.rs index 28dd6ac5a..35080fa99 100644 --- a/modules/fundamental/src/sbom/service/sbom.rs +++ b/modules/fundamental/src/sbom/service/sbom.rs @@ -1,7 +1,7 @@ use super::SbomService; -use crate::sbom::model::LicenseRefMapping; use crate::{ Error, + common::LicenseRefMapping, sbom::model::{ SbomExternalPackageReference, SbomNodeReference, SbomPackage, SbomPackageRelation, SbomSummary, Which, details::SbomDetails, @@ -235,7 +235,8 @@ impl SbomService { } /// Get all the tuples License ID, License Name from the licensing_infos table for a single SBOM - async fn get_licensing_infos( + #[instrument(skip(connection), err(level=tracing::Level::INFO))] + pub async fn get_licensing_infos( connection: &C, sbom_id: Uuid, ) -> Result, Error> { @@ -624,9 +625,9 @@ where JoinType::LeftJoin, sbom_package::Relation::PackageLicense.def(), ).join( - JoinType::LeftJoin, - sbom_package_license::Relation::License.def(), - ) + JoinType::LeftJoin, + sbom_package_license::Relation::License.def(), + ) } #[derive(FromQueryResult)] diff --git a/openapi.yaml b/openapi.yaml index 3c709db04..7cc66a0c8 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4665,6 +4665,7 @@ components: - base - advisories - licenses + - license_ref_mapping properties: advisories: type: array @@ -4672,10 +4673,14 @@ components: $ref: '#/components/schemas/PurlAdvisory' base: $ref: '#/components/schemas/BasePurlHead' + license_ref_mapping: + type: array + items: + $ref: '#/components/schemas/LicenseRefMapping' licenses: type: array items: - $ref: '#/components/schemas/PurlLicenseSummary' + $ref: '#/components/schemas/PurlLicenseInfo' version: $ref: '#/components/schemas/VersionedPurlHead' PurlHead: @@ -4691,18 +4696,16 @@ components: type: string format: uuid description: The ID of the qualified PURL - PurlLicenseSummary: + PurlLicenseInfo: type: object required: - - sbom - - licenses + - license_name + - license_type properties: - licenses: - type: array - items: - type: string - sbom: - $ref: '#/components/schemas/SbomHead' + license_name: + type: string + license_type: + type: string PurlStatus: type: object required: From f64a9d28bb5547e319c7e97618cd246b770853dc Mon Sep 17 00:00:00 2001 From: "bxf12315@gmail.com" Date: Mon, 15 Sep 2025 20:57:50 +0800 Subject: [PATCH 2/4] Replace the SQL approach with an ORM approach. --- .../src/purl/model/details/purl.rs | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/modules/fundamental/src/purl/model/details/purl.rs b/modules/fundamental/src/purl/model/details/purl.rs index cf69c7a75..9613664bf 100644 --- a/modules/fundamental/src/purl/model/details/purl.rs +++ b/modules/fundamental/src/purl/model/details/purl.rs @@ -8,8 +8,8 @@ use crate::{ }; use sea_orm::{ ColumnTrait, ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Iterable, LoaderTrait, - ModelTrait, QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, RelationTrait, - Select, Statement, + ModelTrait, Order, QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, + RelationTrait, Select, }; use sea_query::{Asterisk, ColumnRef, Expr, Func, IntoIden, JoinType, SimpleExpr}; use serde::{Deserialize, Serialize}; @@ -25,7 +25,8 @@ use trustify_entity::sbom_package_license::LicenseCategory; use trustify_entity::{ advisory, base_purl, cpe, cvss3, license, organization, product, product_status, product_version, product_version_range, purl_status, qualified_purl, sbom, sbom_package, - sbom_package_purl_ref, status, version_range, versioned_purl, vulnerability, + sbom_package_license, sbom_package_purl_ref, status, version_range, versioned_purl, + vulnerability, }; use trustify_module_ingestor::common::{Deprecation, DeprecationForExt}; use utoipa::ToSchema; @@ -122,26 +123,29 @@ impl PurlDetails { ) .await?; - let purl_license_results: Vec = - PurlLicenseResult::find_by_statement(Statement::from_sql_and_values( - tx.get_database_backend(), - r#" - SELECT DISTINCT - l.text as license_name, - qp.id as qualified_package_id, - spl.license_type, - sp.sbom_id, - l.id as license_id - FROM qualified_purl qp - JOIN sbom_package_purl_ref sppr ON qp.id = sppr.qualified_purl_id - JOIN sbom_package sp ON sppr.sbom_id = sp.sbom_id AND sppr.node_id = sp.node_id - JOIN sbom_package_license spl ON sp.sbom_id = spl.sbom_id AND sp.node_id = spl.node_id - JOIN license l ON spl.license_id = l.id - WHERE qp.purl = $1 - ORDER BY spl.license_type, sp.sbom_id, l.text - "#, - [qualified_package.clone().purl.into()], - )) + let purl_license_results: Vec = qualified_purl::Entity::find() + .filter(qualified_purl::Column::Purl.eq(qualified_package.clone().purl)) + .join(JoinType::Join, qualified_purl::Relation::SbomPackage.def()) + .join( + JoinType::Join, + sbom_package_purl_ref::Relation::Package.def(), + ) + .join(JoinType::Join, sbom_package::Relation::PackageLicense.def()) + .join( + JoinType::Join, + sbom_package_license::Relation::License.def(), + ) + .select_only() + .column_as(license::Column::Text, "license_name") + .column_as(qualified_purl::Column::Id, "qualified_package_id") + .column_as(sbom_package_license::Column::LicenseType, "license_type") + .column_as(sbom_package::Column::SbomId, "sbom_id") + .column_as(license::Column::Id, "license_id") + .distinct() + .order_by(sbom_package_license::Column::LicenseType, Order::Asc) + .order_by(sbom_package::Column::SbomId, Order::Asc) + .order_by(license::Column::Text, Order::Asc) + .into_model::() .all(tx) .await?; From 48eb0ddd01e743d2641574ccbd655ae8deb82758 Mon Sep 17 00:00:00 2001 From: mrizzi Date: Mon, 15 Sep 2025 16:02:49 +0200 Subject: [PATCH 3/4] TC-2826: refactored LicenseInfo Signed-off-by: mrizzi --- modules/fundamental/src/common/mod.rs | 18 ++++++ .../fundamental/src/purl/endpoints/test.rs | 4 +- .../src/purl/model/details/purl.rs | 55 ++++++------------- modules/fundamental/src/sbom/model/mod.rs | 22 +------- modules/fundamental/tests/sbom/spdx/perf.rs | 2 +- openapi.yaml | 12 +--- 6 files changed, 43 insertions(+), 70 deletions(-) diff --git a/modules/fundamental/src/common/mod.rs b/modules/fundamental/src/common/mod.rs index 1fc086fa4..8efa22233 100644 --- a/modules/fundamental/src/common/mod.rs +++ b/modules/fundamental/src/common/mod.rs @@ -1,5 +1,8 @@ +use crate::sbom::service::sbom::LicenseBasicInfo; use sea_orm::FromQueryResult; +use sea_query::FromValueTuple; use serde::{Deserialize, Serialize}; +use trustify_entity::sbom_package_license::LicenseCategory; use utoipa::ToSchema; pub mod service; @@ -9,3 +12,18 @@ pub struct LicenseRefMapping { pub license_id: String, pub license_name: String, } + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)] +pub struct LicenseInfo { + pub license_name: String, + pub license_type: LicenseCategory, +} + +impl From for LicenseInfo { + fn from(license_basic_info: LicenseBasicInfo) -> Self { + LicenseInfo { + license_name: license_basic_info.license_name, + license_type: LicenseCategory::from_value_tuple(license_basic_info.license_type), + } + } +} diff --git a/modules/fundamental/src/purl/endpoints/test.rs b/modules/fundamental/src/purl/endpoints/test.rs index c643a1262..f99b6bc5e 100644 --- a/modules/fundamental/src/purl/endpoints/test.rs +++ b/modules/fundamental/src/purl/endpoints/test.rs @@ -281,11 +281,11 @@ async fn test_purl_license_details(ctx: &TrustifyContext) -> Result<(), anyhow:: "licenses": [ { "license_name": "(LicenseRef-8 OR LicenseRef-0 OR LicenseRef-MPL) AND (LicenseRef-Netscape OR LicenseRef-0 OR LicenseRef-8)", - "license_type": "Declared" + "license_type": "declared" }, { "license_name": "NOASSERTION", - "license_type": "Concluded" + "license_type": "concluded" } ], "license_ref_mapping": [ diff --git a/modules/fundamental/src/purl/model/details/purl.rs b/modules/fundamental/src/purl/model/details/purl.rs index 9613664bf..824726820 100644 --- a/modules/fundamental/src/purl/model/details/purl.rs +++ b/modules/fundamental/src/purl/model/details/purl.rs @@ -1,3 +1,5 @@ +use crate::common::LicenseInfo; +use crate::sbom::service::sbom::LicenseBasicInfo; use crate::{ Error, advisory::model::AdvisoryHead, @@ -8,8 +10,8 @@ use crate::{ }; use sea_orm::{ ColumnTrait, ConnectionTrait, DbErr, EntityTrait, FromQueryResult, Iterable, LoaderTrait, - ModelTrait, Order, QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, - RelationTrait, Select, + ModelTrait, QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, RelationTrait, + Select, SelectColumns, }; use sea_query::{Asterisk, ColumnRef, Expr, Func, IntoIden, JoinType, SimpleExpr}; use serde::{Deserialize, Serialize}; @@ -21,7 +23,6 @@ use trustify_common::{ purl::Purl, }; use trustify_cvss::cvss3::{Cvss3Base, score::Score, severity::Severity}; -use trustify_entity::sbom_package_license::LicenseCategory; use trustify_entity::{ advisory, base_purl, cpe, cvss3, license, organization, product, product_status, product_version, product_version_range, purl_status, qualified_purl, sbom, sbom_package, @@ -39,32 +40,15 @@ pub struct PurlDetails { pub version: VersionedPurlHead, pub base: BasePurlHead, pub advisories: Vec, - pub licenses: Vec, + pub licenses: Vec, pub license_ref_mapping: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema, Default)] -pub struct PurlLicenseInfo { - pub license_name: String, - pub license_type: String, -} - -impl PurlLicenseInfo { - pub fn from_purl_license_result(plr: PurlLicenseResult) -> Self { - PurlLicenseInfo { - license_name: plr.license_name, - license_type: plr.license_type.show().to_string(), - } - } -} - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema, FromQueryResult)] pub struct PurlLicenseResult { pub sbom_id: Uuid, - pub license_id: Uuid, - pub qualified_package_id: Uuid, pub license_name: String, - pub license_type: LicenseCategory, + pub license_type: i32, } impl PurlDetails { @@ -123,9 +107,13 @@ impl PurlDetails { ) .await?; - let purl_license_results: Vec = qualified_purl::Entity::find() - .filter(qualified_purl::Column::Purl.eq(qualified_package.clone().purl)) - .join(JoinType::Join, qualified_purl::Relation::SbomPackage.def()) + let purl_license_results: Vec = sbom_package_purl_ref::Entity::find() + .distinct() + .select_only() + .select_column(sbom_package::Column::SbomId) + .select_column_as(license::Column::Text, "license_name") + .select_column(sbom_package_license::Column::LicenseType) + .filter(sbom_package_purl_ref::Column::QualifiedPurlId.eq(qualified_package.id)) .join( JoinType::Join, sbom_package_purl_ref::Relation::Package.def(), @@ -135,17 +123,7 @@ impl PurlDetails { JoinType::Join, sbom_package_license::Relation::License.def(), ) - .select_only() - .column_as(license::Column::Text, "license_name") - .column_as(qualified_purl::Column::Id, "qualified_package_id") - .column_as(sbom_package_license::Column::LicenseType, "license_type") - .column_as(sbom_package::Column::SbomId, "sbom_id") - .column_as(license::Column::Id, "license_id") - .distinct() - .order_by(sbom_package_license::Column::LicenseType, Order::Asc) - .order_by(sbom_package::Column::SbomId, Order::Asc) - .order_by(license::Column::Text, Order::Asc) - .into_model::() + .into_model() .all(tx) .await?; @@ -159,7 +137,10 @@ impl PurlDetails { &licensing_infos, &mut license_ref_mapping, ); - purl_license_info.push(PurlLicenseInfo::from_purl_license_result(plr)); + purl_license_info.push(LicenseInfo::from(LicenseBasicInfo { + license_name: plr.license_name, + license_type: plr.license_type, + })); } Ok(PurlDetails { diff --git a/modules/fundamental/src/sbom/model/mod.rs b/modules/fundamental/src/sbom/model/mod.rs index e1975eb19..21327cd7d 100644 --- a/modules/fundamental/src/sbom/model/mod.rs +++ b/modules/fundamental/src/sbom/model/mod.rs @@ -2,19 +2,18 @@ pub mod details; pub mod raw_sql; use super::service::SbomService; +use crate::common::LicenseInfo; use crate::{ Error, common::LicenseRefMapping, purl::model::summary::purl::PurlSummary, - sbom::service::sbom::LicenseBasicInfo, source_document::model::SourceDocument, + source_document::model::SourceDocument, }; use sea_orm::{ConnectionTrait, ModelTrait, PaginatorTrait, prelude::Uuid}; -use sea_query::FromValueTuple; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use tracing::instrument; use trustify_common::{cpe::Cpe, model::Paginated, purl::Purl}; use trustify_entity::{ - labels::Labels, relationship::Relationship, sbom, sbom_node, sbom_package, - sbom_package_license::LicenseCategory, source_document, + labels::Labels, relationship::Relationship, sbom, sbom_node, sbom_package, source_document, }; use utoipa::ToSchema; @@ -138,21 +137,6 @@ pub struct SbomPackage { pub licenses_ref_mapping: Vec, } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema)] -pub struct LicenseInfo { - pub license_name: String, - pub license_type: LicenseCategory, -} - -impl From for LicenseInfo { - fn from(license_basic_info: LicenseBasicInfo) -> Self { - LicenseInfo { - license_name: license_basic_info.license_name, - license_type: LicenseCategory::from_value_tuple(license_basic_info.license_type), - } - } -} - #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum SbomPackageReference<'a> { Internal(&'a str), diff --git a/modules/fundamental/tests/sbom/spdx/perf.rs b/modules/fundamental/tests/sbom/spdx/perf.rs index 55b6c2d9b..0d297a1e4 100644 --- a/modules/fundamental/tests/sbom/spdx/perf.rs +++ b/modules/fundamental/tests/sbom/spdx/perf.rs @@ -4,7 +4,7 @@ use test_log::test; use tracing::instrument; use trustify_common::model::Paginated; use trustify_entity::sbom_package_license::LicenseCategory; -use trustify_module_fundamental::sbom::model::{LicenseInfo, SbomPackage}; +use trustify_module_fundamental::{common::LicenseInfo, sbom::model::SbomPackage}; use trustify_test_context::TrustifyContext; #[test_context(TrustifyContext)] diff --git a/openapi.yaml b/openapi.yaml index 7cc66a0c8..61525638d 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4680,7 +4680,7 @@ components: licenses: type: array items: - $ref: '#/components/schemas/PurlLicenseInfo' + $ref: '#/components/schemas/LicenseInfo' version: $ref: '#/components/schemas/VersionedPurlHead' PurlHead: @@ -4696,16 +4696,6 @@ components: type: string format: uuid description: The ID of the qualified PURL - PurlLicenseInfo: - type: object - required: - - license_name - - license_type - properties: - license_name: - type: string - license_type: - type: string PurlStatus: type: object required: From 1f22bc92b6f9f91c668d0fba2fb2d576aef23783 Mon Sep 17 00:00:00 2001 From: mrizzi Date: Mon, 15 Sep 2025 17:33:08 +0200 Subject: [PATCH 4/4] TC-2826: removed unused 'show' function Signed-off-by: mrizzi --- entity/src/sbom_package_license.rs | 18 ------------------ modules/fundamental/src/purl/endpoints/test.rs | 2 +- .../fundamental/src/purl/model/details/purl.rs | 4 ++-- modules/fundamental/src/sbom/endpoints/mod.rs | 1 - openapi.yaml | 10 +++++----- 5 files changed, 8 insertions(+), 27 deletions(-) diff --git a/entity/src/sbom_package_license.rs b/entity/src/sbom_package_license.rs index d285b1c3e..bd9e79dfb 100644 --- a/entity/src/sbom_package_license.rs +++ b/entity/src/sbom_package_license.rs @@ -68,21 +68,3 @@ impl Related for Entity { } impl ActiveModelBehavior for ActiveModel {} - -impl LicenseCategory { - /// Returns the string representation of the license category - /// - /// # Examples - /// ``` - /// use trustify_entity::sbom_package_license::LicenseCategory; - /// - /// assert_eq!(LicenseCategory::Declared.show(), "Declared"); - /// assert_eq!(LicenseCategory::Concluded.show(), "Concluded"); - /// ``` - pub fn show(&self) -> &'static str { - match self { - LicenseCategory::Declared => "Declared", - LicenseCategory::Concluded => "Concluded", - } - } -} diff --git a/modules/fundamental/src/purl/endpoints/test.rs b/modules/fundamental/src/purl/endpoints/test.rs index f99b6bc5e..694e96638 100644 --- a/modules/fundamental/src/purl/endpoints/test.rs +++ b/modules/fundamental/src/purl/endpoints/test.rs @@ -288,7 +288,7 @@ async fn test_purl_license_details(ctx: &TrustifyContext) -> Result<(), anyhow:: "license_type": "concluded" } ], - "license_ref_mapping": [ + "licenses_ref_mapping": [ { "license_id": "LicenseRef-Netscape", "license_name": "Netscape" diff --git a/modules/fundamental/src/purl/model/details/purl.rs b/modules/fundamental/src/purl/model/details/purl.rs index 824726820..127e2e7f1 100644 --- a/modules/fundamental/src/purl/model/details/purl.rs +++ b/modules/fundamental/src/purl/model/details/purl.rs @@ -41,7 +41,7 @@ pub struct PurlDetails { pub base: BasePurlHead, pub advisories: Vec, pub licenses: Vec, - pub license_ref_mapping: Vec, + pub licenses_ref_mapping: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ToSchema, FromQueryResult)] @@ -149,7 +149,7 @@ impl PurlDetails { base: BasePurlHead::from_entity(&package), advisories: PurlAdvisory::from_entities(purl_statuses, product_statuses, tx).await?, licenses: purl_license_info, - license_ref_mapping, + licenses_ref_mapping: license_ref_mapping, }) } } diff --git a/modules/fundamental/src/sbom/endpoints/mod.rs b/modules/fundamental/src/sbom/endpoints/mod.rs index c53a8e4a9..fc00c7b41 100644 --- a/modules/fundamental/src/sbom/endpoints/mod.rs +++ b/modules/fundamental/src/sbom/endpoints/mod.rs @@ -6,7 +6,6 @@ mod test; pub use query::*; -use crate::sbom::model::LicenseRefMapping; use crate::{ Error, common::{LicenseRefMapping, service::delete_doc}, diff --git a/openapi.yaml b/openapi.yaml index 61525638d..43b11fb7c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4665,7 +4665,7 @@ components: - base - advisories - licenses - - license_ref_mapping + - licenses_ref_mapping properties: advisories: type: array @@ -4673,14 +4673,14 @@ components: $ref: '#/components/schemas/PurlAdvisory' base: $ref: '#/components/schemas/BasePurlHead' - license_ref_mapping: - type: array - items: - $ref: '#/components/schemas/LicenseRefMapping' licenses: type: array items: $ref: '#/components/schemas/LicenseInfo' + licenses_ref_mapping: + type: array + items: + $ref: '#/components/schemas/LicenseRefMapping' version: $ref: '#/components/schemas/VersionedPurlHead' PurlHead: