use std::{collections::HashMap, fmt::Display, path::PathBuf};
use crate::GHASError;
use crate::codeql::CodeQLLanguage;
use crate::codeql::database::queries::CodeQLQueries;
use super::models::CodeQLPackType;
use super::{PackYaml, PackYamlLock};
#[derive(Debug, Clone, Default)]
pub struct CodeQLPack {
pub(crate) queries: CodeQLQueries,
pub(crate) path: PathBuf,
pub(crate) pack: Option<PackYaml>,
pub(crate) pack_type: Option<CodeQLPackType>,
pub(crate) pack_lock: Option<PackYamlLock>,
}
impl CodeQLPack {
pub fn new(pack: impl Into<String>) -> Self {
let pack = pack.into();
if let Ok(path) = PathBuf::from(&pack).canonicalize() {
return Self::load(path).unwrap_or_default();
} else {
Self::load_remote_pack(pack).unwrap_or_default()
}
}
pub fn name(&self) -> String {
self.queries.name().unwrap_or_default()
}
pub fn namespace(&self) -> String {
self.queries.scope().unwrap_or_else(|| "codeql".to_string())
}
pub fn full_name(&self) -> String {
let mut full_name = format!("{}/{}", self.namespace(), self.name());
if let Some(version) = self.queries.range() {
full_name.push_str(&format!("@{}", version));
}
if let Some(suite) = self.queries.suite() {
full_name.push_str(&format!(":{}", suite));
}
full_name
}
pub fn path(&self) -> PathBuf {
self.path.clone()
}
pub fn version(&self) -> Option<String> {
if let Some(version) = &self.queries.range() {
return Some(version.clone());
} else if let Some(pack) = &self.pack {
return pack.version.clone();
}
None
}
pub fn language(&self) -> Option<CodeQLLanguage> {
if let Some(pack) = &self.pack {
if let Some(extractor) = &pack.extractor {
return Some(CodeQLLanguage::from(extractor.as_str()));
} else if let Some(targets) = &pack.extension_targets {
if let Some((_, lang)) = targets.iter().next() {
return Some(CodeQLLanguage::from(lang.as_str()));
}
}
}
None
}
pub fn suite(&self) -> Option<String> {
if let Some(pack) = &self.pack {
return pack.default_suite_file.clone();
}
None
}
pub async fn is_installed(&self) -> bool {
self.path.exists()
}
pub fn dependencies(&self) -> HashMap<String, String> {
if let Some(pack_lock) = &self.pack_lock {
pack_lock
.dependencies
.iter()
.map(|(key, value)| (key.clone(), value.version.clone()))
.collect()
} else if let Some(pack) = &self.pack {
pack.dependencies.clone().unwrap_or_default()
} else {
HashMap::new()
}
}
pub fn pack_type(&self) -> CodeQLPackType {
self.pack_type.clone().unwrap_or_default()
}
#[cfg(feature = "async")]
pub async fn download(&self, codeql: &crate::CodeQL) -> Result<(), GHASError> {
log::debug!("Downloading CodeQL Pack: {}", self.full_name());
codeql
.run(vec!["pack", "download", self.full_name().as_str()])
.await?;
Ok(())
}
pub async fn download_pack(
codeql: &crate::CodeQL,
name: impl Into<String>,
) -> Result<Self, GHASError> {
let name = name.into();
log::debug!("Downloading CodeQL Pack: {name}");
let pack = CodeQLPack::try_from(name.clone())?;
pack.download(codeql).await?;
Ok(pack)
}
#[cfg(feature = "async")]
pub async fn install(&self, codeql: &crate::CodeQL) -> Result<(), GHASError> {
codeql
.run(vec!["pack", "install", self.path().to_str().unwrap()])
.await
.map(|_| ())
}
#[cfg(feature = "async")]
pub async fn upgrade(&self, codeql: &crate::CodeQL) -> Result<(), GHASError> {
codeql
.run(vec!["pack", "upgrade", self.path().to_str().unwrap()])
.await
.map(|_| ())
}
#[cfg(feature = "async")]
pub async fn publish(
&self,
codeql: &crate::CodeQL,
token: impl Into<String>,
) -> Result<(), GHASError> {
Ok(tokio::process::Command::new(codeql.path())
.env("CODEQL_REGISTRIES_AUTH", token.into())
.args(vec!["pack", "publish", self.path().to_str().unwrap()])
.output()
.await
.map(|_| ())?)
}
pub(crate) fn get_pack_type(pack_yaml: &PackYaml) -> CodeQLPackType {
if let Some(library) = pack_yaml.library {
if library && pack_yaml.data_extensions.is_some() {
return CodeQLPackType::Models;
} else if library {
return CodeQLPackType::Library;
}
} else if pack_yaml.tests.is_some() {
return CodeQLPackType::Testing;
} else if pack_yaml.data_extensions.is_some() {
return CodeQLPackType::Models;
}
CodeQLPackType::Queries
}
}
impl Display for CodeQLPack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(version) = self.version() {
write!(f, "{} ({}) - v{}", self.name(), self.pack_type(), version)
} else {
write!(f, "{} ({})", self.name(), self.pack_type(),)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_codeql_pack_display() {
let pack = CodeQLPack::new("codeql/javascript-queries@1.0.0");
assert_eq!(pack.to_string(), "javascript-queries (Queries) - v1.0.0");
}
#[test]
fn test_codeql_pack_full_name() {
let pack = CodeQLPack::new("codeql/javascript-queries@1.0.0");
assert_eq!(pack.full_name(), "codeql/javascript-queries@1.0.0");
}
#[test]
fn test_codeql_pack_namespace() {
let pack = CodeQLPack::new("codeql/javascript-queries");
assert_eq!(pack.namespace(), "codeql");
assert_eq!(pack.name(), "javascript-queries");
assert_eq!(pack.full_name(), "codeql/javascript-queries");
}
}