use serde::Deserialize;
use super::SourceAdapter;
use crate::model::{Match, Query, Source};
use crate::Result;
const DEFAULT_BASE_URL: &str = "https://artifacthub.io";
#[derive(Debug, Clone)]
pub struct ArtifactHub {
client: reqwest::Client,
base_url: String,
}
impl ArtifactHub {
pub fn new(client: reqwest::Client) -> Self {
Self::with_base_url(client, DEFAULT_BASE_URL.to_string())
}
pub fn with_base_url(client: reqwest::Client, base_url: String) -> Self {
Self { client, base_url }
}
}
#[derive(Debug, Deserialize)]
struct SearchResponse {
#[serde(default)]
packages: Vec<PackageHit>,
}
#[derive(Debug, Deserialize)]
struct PackageHit {
name: String,
#[serde(default)]
description: Option<String>,
#[serde(default)]
stars: Option<u64>,
repository: Repository,
}
#[derive(Debug, Deserialize)]
struct Repository {
name: String,
kind: u32,
#[serde(default)]
url: Option<String>,
}
fn kind_slug(kind: u32) -> Option<&'static str> {
Some(match kind {
0 => "helm",
1 => "falco",
2 => "opa",
3 => "olm",
4 => "tbaction",
5 => "krew",
6 => "helm-plugin",
7 => "tekton-task",
8 => "keda-scaler",
9 => "coredns",
10 => "keptn",
11 => "tekton-pipeline",
12 => "container",
13 => "kubewarden",
14 => "gatekeeper",
15 => "kyverno",
16 => "knative-client-plugin",
17 => "backstage",
18 => "argo-template",
19 => "kubearmor",
20 => "kcl",
21 => "headlamp",
22 => "inspektor-gadget",
23 => "tekton-stepaction",
24 => "meshery",
25 => "opencost",
26 => "radius",
27 => "bootc",
_ => return None,
})
}
fn package_url(base_url: &str, repo: &Repository, name: &str) -> String {
match kind_slug(repo.kind) {
Some(slug) => format!("{base_url}/packages/{slug}/{}/{name}", repo.name),
None => repo.url.clone().unwrap_or_default(),
}
}
#[async_trait::async_trait]
impl SourceAdapter for ArtifactHub {
fn id(&self) -> Source {
Source::ArtifactHub
}
async fn search(&self, query: &Query) -> Result<Vec<Match>> {
let url = format!("{}/api/v1/packages/search", self.base_url);
let q = query.keywords.join(" ");
let body: SearchResponse = self
.client
.get(&url)
.query(&[
("ts_query_web", q.as_str()),
("limit", "15"),
("facets", "false"),
])
.send()
.await?
.error_for_status()?
.json()
.await?;
Ok(body
.packages
.into_iter()
.map(|p| {
let url = package_url(&self.base_url, &p.repository, &p.name);
Match {
url,
name: p.name,
source: Source::ArtifactHub,
description: p.description.unwrap_or_default(),
popularity: p.stars.filter(|&s| s > 0),
similarity: 0.0,
}
})
.collect())
}
}