use std::collections::HashMap;
use tracing::{debug, info, instrument};
use super::types::{AssetType, CatalogComponent, HfComponentCategory};
#[derive(Debug, Clone, Default)]
pub struct HfCatalog {
pub(crate) components: HashMap<String, CatalogComponent>,
}
impl HfCatalog {
pub fn new() -> Self {
Self::default()
}
pub fn standard() -> Self {
let mut catalog = Self::new();
catalog.register_hub_components();
catalog.register_deployment_components();
catalog.register_library_components();
catalog.register_training_components();
catalog.register_collaboration_components();
catalog.register_community_components();
catalog.register_integration_components();
catalog
}
pub fn add(&mut self, component: CatalogComponent) {
self.components.insert(component.id.clone(), component);
}
#[instrument(name = "hf.catalog.get", skip(self), fields(found = tracing::field::Empty))]
pub fn get(&self, id: &str) -> Option<&CatalogComponent> {
let result = self.components.get(id);
tracing::Span::current().record("found", result.is_some());
if result.is_some() {
debug!(component_id = id, "Retrieved catalog component");
}
result
}
pub fn list(&self) -> Vec<&str> {
let mut ids: Vec<_> = self.components.keys().map(String::as_str).collect();
ids.sort_unstable();
ids
}
pub fn len(&self) -> usize {
self.components.len()
}
pub fn is_empty(&self) -> bool {
self.components.is_empty()
}
pub fn all(&self) -> impl Iterator<Item = &CatalogComponent> {
self.components.values()
}
#[instrument(name = "hf.catalog.by_tag", skip(self), fields(result_count = tracing::field::Empty))]
pub fn by_tag(&self, tag: &str) -> Vec<&CatalogComponent> {
let tag_lower = tag.to_lowercase();
let results: Vec<_> = self
.components
.values()
.filter(|c| c.tags.iter().any(|t| t.to_lowercase() == tag_lower))
.collect();
tracing::Span::current().record("result_count", results.len());
debug!(tag = tag, count = results.len(), "Tag query completed");
results
}
#[instrument(name = "hf.catalog.by_category", skip(self), fields(result_count = tracing::field::Empty))]
pub fn by_category(&self, category: HfComponentCategory) -> Vec<&CatalogComponent> {
let results: Vec<_> = self.components.values().filter(|c| c.category == category).collect();
tracing::Span::current().record("result_count", results.len());
debug!(
category = ?category,
count = results.len(),
"Category query completed"
);
results
}
#[instrument(name = "hf.catalog.search", skip(self), fields(result_count = tracing::field::Empty))]
pub fn search(&self, query: &str) -> Vec<&CatalogComponent> {
let query_lower = query.to_lowercase();
let results: Vec<_> = self
.components
.values()
.filter(|c| {
c.id.to_lowercase().contains(&query_lower)
|| c.name.to_lowercase().contains(&query_lower)
|| c.description.to_lowercase().contains(&query_lower)
|| c.tags.iter().any(|t| t.to_lowercase().contains(&query_lower))
})
.collect();
tracing::Span::current().record("result_count", results.len());
info!(query = query, count = results.len(), "Catalog search completed");
results
}
#[instrument(name = "hf.catalog.by_course", skip(self), fields(result_count = tracing::field::Empty))]
pub fn by_course(&self, course: u8) -> Vec<&CatalogComponent> {
let results: Vec<_> = self
.components
.values()
.filter(|c| c.courses.iter().any(|ca| ca.course == course))
.collect();
tracing::Span::current().record("result_count", results.len());
info!(course = course, count = results.len(), "Course query completed");
results
}
#[instrument(name = "hf.catalog.by_course_week", skip(self), fields(result_count = tracing::field::Empty))]
pub fn by_course_week(&self, course: u8, week: u8) -> Vec<&CatalogComponent> {
let results: Vec<_> = self
.components
.values()
.filter(|c| c.courses.iter().any(|ca| ca.course == course && ca.week == week))
.collect();
tracing::Span::current().record("result_count", results.len());
debug!(course = course, week = week, count = results.len(), "Course-week query completed");
results
}
#[instrument(name = "hf.catalog.by_asset_type", skip(self), fields(result_count = tracing::field::Empty))]
pub fn by_asset_type(&self, asset: AssetType) -> Vec<&CatalogComponent> {
let results: Vec<_> = self
.components
.values()
.filter(|c| c.courses.iter().any(|ca| ca.asset_types.contains(&asset)))
.collect();
tracing::Span::current().record("result_count", results.len());
debug!(asset = ?asset, count = results.len(), "Asset type query completed");
results
}
#[instrument(name = "hf.catalog.deps", skip(self), fields(result_count = tracing::field::Empty))]
pub fn deps(&self, id: &str) -> Vec<&CatalogComponent> {
let results = self
.get(id)
.map(|c| {
c.dependencies
.iter()
.filter_map(|dep_id| self.components.get(dep_id))
.collect::<Vec<_>>()
})
.unwrap_or_default();
tracing::Span::current().record("result_count", results.len());
debug!(component_id = id, dep_count = results.len(), "Dependency lookup completed");
results
}
#[instrument(name = "hf.catalog.rdeps", skip(self), fields(result_count = tracing::field::Empty))]
pub fn rdeps(&self, id: &str) -> Vec<&CatalogComponent> {
let results: Vec<_> =
self.components.values().filter(|c| c.dependencies.contains(&id.to_string())).collect();
tracing::Span::current().record("result_count", results.len());
debug!(
component_id = id,
rdep_count = results.len(),
"Reverse dependency lookup completed"
);
results
}
pub fn compatible(&self, id1: &str, id2: &str) -> bool {
self.get(id1).is_some() && self.get(id2).is_some()
}
pub fn docs_url(&self, id: &str) -> Option<&str> {
self.get(id).map(|c| c.docs_url.as_str())
}
pub fn api_url(&self, id: &str) -> Option<String> {
self.docs_url(id).map(|url| format!("{}/api", url.trim_end_matches('/')))
}
pub fn tutorials_url(&self, id: &str) -> Option<String> {
self.docs_url(id).map(|url| format!("{}/tutorials", url.trim_end_matches('/')))
}
}