use std::collections::HashMap;
use std::sync::Arc;
type ValidatorFn = Arc<dyn Fn(&serde_json::Value) -> bool + Send + Sync>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ComponentId(pub String);
impl ComponentId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
}
impl std::fmt::Display for ComponentId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<&str> for ComponentId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Default)]
pub struct ComponentRegistry {
templates: HashMap<ComponentId, String>,
insertion_order: Vec<ComponentId>,
validators: HashMap<ComponentId, ValidatorFn>,
}
impl ComponentRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn with_standard_components() -> Self {
let mut registry = Self::new();
registry.register_standard_components();
registry
}
pub fn register_standard_components(&mut self) {
self.register(
ComponentId::new("score-card"),
include_str!("../../templates/components/score_card.typ").to_string(),
);
self.register(
ComponentId::new("finding"),
include_str!("../../templates/components/finding.typ").to_string(),
);
self.register(
ComponentId::new("audit-table"),
include_str!("../../templates/components/audit_table.typ").to_string(),
);
self.register(
ComponentId::new("section"),
include_str!("../../templates/components/section.typ").to_string(),
);
self.register(
ComponentId::new("image"),
include_str!("../../templates/components/image.typ").to_string(),
);
self.register(
ComponentId::new("callout"),
include_str!("../../templates/components/callout.typ").to_string(),
);
self.register(
ComponentId::new("summary-box"),
include_str!("../../templates/components/summary_box.typ").to_string(),
);
self.register(
ComponentId::new("list"),
include_str!("../../templates/components/list.typ").to_string(),
);
self.register(
ComponentId::new("divider"),
include_str!("../../templates/components/divider.typ").to_string(),
);
self.register(
ComponentId::new("progress-bar"),
include_str!("../../templates/components/progress_bar.typ").to_string(),
);
self.register(
ComponentId::new("key-value-list"),
include_str!("../../templates/components/key_value_list.typ").to_string(),
);
self.register(
ComponentId::new("watermark"),
include_str!("../../templates/components/watermark.typ").to_string(),
);
self.register(
ComponentId::new("page-break"),
include_str!("../../templates/components/page_break.typ").to_string(),
);
self.register(
ComponentId::new("chart"),
include_str!("../../templates/components/chart.typ").to_string(),
);
self.register(
ComponentId::new("sparkline"),
include_str!("../../templates/components/sparkline.typ").to_string(),
);
self.register(
ComponentId::new("gauge"),
include_str!("../../templates/components/gauge.typ").to_string(),
);
self.register(
ComponentId::new("crosstab"),
include_str!("../../templates/components/crosstab.typ").to_string(),
);
self.register(
ComponentId::new("pivot-table"),
include_str!("../../templates/components/pivot_table.typ").to_string(),
);
self.register(
ComponentId::new("barcode"),
include_str!("../../templates/components/barcode.typ").to_string(),
);
self.register(
ComponentId::new("label"),
include_str!("../../templates/components/label.typ").to_string(),
);
self.register(
ComponentId::new("textblock"),
include_str!("../../templates/components/text.typ").to_string(),
);
self.register(
ComponentId::new("number-field"),
include_str!("../../templates/components/number_field.typ").to_string(),
);
self.register(
ComponentId::new("date-field"),
include_str!("../../templates/components/date_field.typ").to_string(),
);
self.register(
ComponentId::new("resource-field"),
include_str!("../../templates/components/resource_field.typ").to_string(),
);
self.register(
ComponentId::new("table-of-contents"),
include_str!("../../templates/components/table_of_contents.typ").to_string(),
);
self.register(
ComponentId::new("metric-card"),
include_str!("../../templates/components/metric_card.typ").to_string(),
);
self.register(
ComponentId::new("hero-summary"),
include_str!("../../templates/components/hero_summary.typ").to_string(),
);
self.register(
ComponentId::new("module-dashboard"),
include_str!("../../templates/components/module_dashboard.typ").to_string(),
);
self.register(
ComponentId::new("action-roadmap"),
include_str!("../../templates/components/action_roadmap.typ").to_string(),
);
self.register(
ComponentId::new("severity-overview"),
include_str!("../../templates/components/severity_overview.typ").to_string(),
);
self.register(
ComponentId::new("cover-page"),
include_str!("../../templates/components/cover_page.typ").to_string(),
);
self.register(
ComponentId::new("module-comparison"),
include_str!("../../templates/components/module_comparison.typ").to_string(),
);
self.register(
ComponentId::new("benchmark-summary"),
include_str!("../../templates/components/benchmark_summary.typ").to_string(),
);
self.register(
ComponentId::new("benchmark-table"),
include_str!("../../templates/components/benchmark_table.typ").to_string(),
);
self.register(
ComponentId::new("grid-component"),
include_str!("../../templates/components/grid.typ").to_string(),
);
self.register(
ComponentId::new("flow-group"),
include_str!("../../templates/components/flow_group.typ").to_string(),
);
}
pub fn register(&mut self, id: ComponentId, template: String) {
if !self.templates.contains_key(&id) {
self.insertion_order.push(id.clone());
}
self.templates.insert(id, template);
}
pub fn register_with_validator(
&mut self,
id: ComponentId,
template: String,
validator: impl Fn(&serde_json::Value) -> bool + Send + Sync + 'static,
) {
self.templates.insert(id.clone(), template);
self.validators.insert(id, Arc::new(validator));
}
pub fn get_template(&self, id: &ComponentId) -> Option<&String> {
self.templates.get(id)
}
pub fn has_component(&self, id: &ComponentId) -> bool {
self.templates.contains_key(id)
}
pub fn validate(&self, id: &ComponentId, data: &serde_json::Value) -> bool {
self.validators.get(id).map(|v| v(data)).unwrap_or(true)
}
pub fn list_components(&self) -> Vec<&ComponentId> {
self.insertion_order.iter().collect()
}
}
impl std::fmt::Debug for ComponentRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ComponentRegistry")
.field("templates", &self.templates.keys().collect::<Vec<_>>())
.field("validators_count", &self.validators.len())
.finish()
}
}