use geekorm::{Connection, prelude::*};
use purl::GenericPurl;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
pub mod snapshots;
use super::{Component, ComponentManager, ComponentType, ComponentVersion};
use crate::bom::sbom::BomComponent;
pub use snapshots::Snapshot;
#[derive(Table, Debug, Default, Clone, Serialize, Deserialize)]
pub struct Dependencies {
#[geekorm(primary_key, auto_increment)]
pub id: PrimaryKey<i32>,
#[geekorm(foreign_key = "Snapshot.id")]
pub snapshot_id: ForeignKey<i32, Snapshot>,
#[geekorm(foreign_key = "Component.id")]
pub component_id: ForeignKey<i32, Component>,
#[geekorm(foreign_key = "ComponentVersion.id")]
pub component_version_id: ForeignKey<i32, ComponentVersion>,
}
impl Dependencies {
pub fn component(&self) -> Component {
self.component_id.data.clone()
}
pub fn component_id(&self) -> PrimaryKey<i32> {
self.component_id.data.id.clone()
}
pub fn component_type(&self) -> ComponentType {
self.component_id.data.component_type.clone()
}
pub fn manager(&self) -> ComponentManager {
self.component_id.data.manager.clone()
}
pub fn name(&self) -> String {
self.component_id.data.name.clone()
}
pub fn namespace(&self) -> Option<String> {
self.component_id.data.namespace.clone()
}
pub fn version(&self) -> Option<String> {
Some(self.component_version_id.data.version.clone())
}
pub fn purl(&self) -> String {
let mut purl = format!("pkg:{}", self.manager());
if let Some(namespace) = &self.namespace() {
purl.push_str(&format!("/{}/{}", namespace, self.name()));
} else {
purl.push_str(&format!("/{}", self.name()));
}
if let Some(version) = &self.version() {
purl.push_str(&format!(":{}", version));
}
return purl;
}
pub async fn from_purl<'a, T>(
connection: &'a T,
value: String,
) -> Result<Self, crate::KonarrError>
where
T: GeekConnection<Connection = T> + 'a,
{
let (mut component, mut version) = Component::from_purl(value)?;
component.find_or_create(connection).await?;
version.component_id = component.id.into();
version.save(connection).await?;
Ok(Dependencies::new(0, component.id, version.id))
}
pub async fn from_bom_compontent<'a, T>(
connection: &'a T,
snapshop: impl Into<PrimaryKey<i32>>,
bom_component: &BomComponent,
) -> Result<Self, crate::KonarrError>
where
T: GeekConnection<Connection = T> + 'a,
{
let snapshop = snapshop.into();
let (mut component, mut version) = Component::from_purl(bom_component.purl.clone())?;
component.find_or_create(connection).await?;
version.component_id = component.id.into();
version.find_or_crate(connection).await?;
match Dependencies::query_first(
connection,
Dependencies::query_select()
.where_eq("snapshot_id", snapshop)
.and()
.where_eq("component_id", component.id)
.and()
.where_eq("component_version_id", version.id)
.build()?,
)
.await
{
Ok(dep) => Ok(dep),
Err(_) => {
let mut new_dep = Dependencies::new(0, component.id, version.id);
new_dep.snapshot_id = snapshop.into();
new_dep.save(connection).await?;
Ok(new_dep)
}
}
}
pub async fn find_or_crate<'a, T>(
&mut self,
connection: &'a T,
) -> Result<(), crate::KonarrError>
where
T: GeekConnection<Connection = T> + 'a,
{
let mut select = Dependencies::query_select()
.where_eq("name", self.name())
.and()
.where_eq("manager", self.manager());
if let Some(namespace) = &self.namespace() {
select = select.and().where_eq("namespace", namespace.clone());
}
let select_final = select.build()?;
match Dependencies::query_first(connection, select_final).await {
Ok(dep) => {
self.id = dep.id;
Ok(())
}
Err(_) => self.save(connection).await.map_err(|e| e.into()),
}
}
pub async fn search<'a, T>(
connection: &'a T,
snapshot_id: impl Into<PrimaryKey<i32>>,
search: impl Into<String>,
) -> Result<Vec<Dependencies>, crate::KonarrError>
where
T: GeekConnection<Connection = T> + 'a,
{
let search = search.into();
let snapshot_id = snapshot_id.into();
let mut query = Component::query_select()
.where_like("name", format!("%{}%", search))
.or()
.where_like("namespace", format!("%{}%", search))
.or()
.where_like("manager", format!("%{}%", search))
.limit(10);
let comps = Component::query(connection, query.build()?).await?;
let mut deps = Vec::new();
for comp in comps {
let mut instances = Dependencies::query(
connection,
Dependencies::query_select()
.where_eq("snapshot_id", snapshot_id)
.where_eq("component_id", comp.id)
.build()?,
)
.await?;
for inst in instances.iter_mut() {
inst.fetch(connection).await?;
}
deps.append(&mut instances);
}
Ok(deps)
}
pub async fn find_by_name<'a, T>(
connection: &'a T,
name: impl Into<String>,
) -> Result<Vec<Dependencies>, crate::KonarrError>
where
T: GeekConnection<Connection = T> + 'a,
{
let name = name.into();
let mut query = Component::query_select()
.where_like("name", format!("%{}%", name))
.or()
.where_like("namespace", format!("%{}%", name))
.or()
.where_like("manager", format!("%{}%", name))
.limit(10);
let comps = Component::query(connection, query.build()?).await?;
let mut deps = Vec::new();
for comp in comps {
let mut instances = Dependencies::fetch_by_component_id(connection, comp.id).await?;
for inst in instances.iter_mut() {
inst.fetch(connection).await?;
}
deps.append(&mut instances);
}
Ok(deps)
}
pub async fn find_by_purl<'a, T>(
connection: &'a T,
purl: impl Into<String>,
) -> Result<Vec<Dependencies>, crate::KonarrError>
where
T: GeekConnection<Connection = T> + 'a,
{
let purl = GenericPurl::<String>::from_str(purl.into().as_str())
.map_err(|e| crate::KonarrError::UnknownError(e.to_string()))?;
let manager = ComponentManager::from(purl.package_type());
let mut query = Component::query_select()
.where_eq("name", purl.name())
.and()
.where_eq("manager", manager);
if let Some(namespace) = purl.namespace() {
query = query.and().where_eq("namespace", namespace);
}
let comps = Component::query(connection, query.build()?).await?;
let mut deps = Vec::new();
for comp in comps {
let mut instances = Dependencies::fetch_by_component_id(connection, comp.id).await?;
for inst in instances.iter_mut() {
inst.fetch(connection).await?;
}
deps.extend(instances);
}
Ok(deps)
}
pub async fn fetch_dependencies_by_snapshop<'a, T>(
connection: &'a T,
snapshop: impl Into<PrimaryKey<i32>>,
) -> Result<Vec<Dependencies>, crate::KonarrError>
where
T: GeekConnection<Connection = T> + 'a,
{
let mut deps = Dependencies::query(
connection,
QueryBuilder::select()
.table(Dependencies::table())
.join(Component::table())
.where_eq("snapshot_id", snapshop.into())
.build()?,
)
.await?;
for dep in deps.iter_mut() {
dep.fetch(connection).await?;
}
Ok(deps)
}
pub async fn fetch_dependency_by_snapshot<'a, T>(
connection: &'a T,
snapshot: impl Into<PrimaryKey<i32>>,
component: impl Into<PrimaryKey<i32>>,
) -> Result<Dependencies, crate::KonarrError>
where
T: GeekConnection<Connection = T> + 'a,
{
Ok(Dependencies::query_first(
connection,
Dependencies::query_select()
.where_eq("snapshot_id", snapshot.into())
.and()
.where_eq("component_id", component.into())
.build()?,
)
.await?)
}
pub async fn count_by_snapshot(
connection: &Connection<'_>,
snapshot: impl Into<PrimaryKey<i32>>,
) -> Result<i64, crate::KonarrError> {
Ok(Dependencies::row_count(
connection,
Dependencies::query_count()
.where_eq("snapshot_id", snapshot.into())
.build()?,
)
.await?)
}
}