systemprompt-templates 0.10.2

Template registry, loading, and rendering for systemprompt.io config-as-code AI governance deployments. Handlebars-powered template engine for the MCP governance pipeline.
Documentation
use std::path::Path;

use systemprompt_template_provider::{PartialSource, TemplateDefinition};
use tracing::{debug, info, warn};

use super::TemplateRegistry;
use crate::TemplateError;

impl TemplateRegistry {
    pub async fn initialize(&mut self) -> Result<(), TemplateError> {
        info!(
            providers = self.providers.len(),
            loaders = self.loaders.len(),
            "Initializing template registry"
        );

        if self.loaders.is_empty() {
            return Err(TemplateError::NotInitialized);
        }

        let mut all_templates: Vec<(TemplateDefinition, &str)> = Vec::new();

        for provider in &self.providers {
            for template in provider.templates() {
                all_templates.push((template, provider.provider_id()));
            }
        }

        all_templates.sort_by_key(|a| a.0.priority);

        for (template, provider_id) in all_templates {
            if self.resolved_templates.contains_key(&template.name) {
                debug!(
                    template = %template.name,
                    provider = %provider_id,
                    "Template already registered, skipping"
                );
                continue;
            }

            debug!(
                template = %template.name,
                provider = %provider_id,
                priority = template.priority,
                "Registering template"
            );

            match self.load_template(&template).await {
                Ok(content) => {
                    if let Err(e) = self
                        .handlebars
                        .register_template_string(&template.name, content)
                    {
                        warn!(
                            template = %template.name,
                            error = %e,
                            "Failed to compile template"
                        );
                        continue;
                    }
                    self.template_sources
                        .insert(template.name.clone(), provider_id.to_string());
                    self.resolved_templates
                        .insert(template.name.clone(), template);
                },
                Err(e) => {
                    warn!(
                        template = %template.name,
                        error = %e,
                        "Failed to load template"
                    );
                },
            }
        }

        self.register_partial_templates().await;

        info!(
            templates = self.resolved_templates.len(),
            "Template registry initialized"
        );

        Ok(())
    }

    async fn register_partial_templates(&mut self) {
        for component in &self.components {
            let Some(partial) = component.partial_template() else {
                continue;
            };

            let content = match &partial.source {
                PartialSource::Embedded(s) => (*s).to_string(),
                PartialSource::File(path) => match self.load_partial_file(path).await {
                    Ok(c) => c,
                    Err(e) => {
                        warn!(
                            component_id = %component.component_id(),
                            path = %path.display(),
                            error = %e,
                            "Failed to load partial template file"
                        );
                        continue;
                    },
                },
            };

            debug!(
                component_id = %component.component_id(),
                partial_name = %partial.name,
                "Registering partial template"
            );

            if let Err(e) = self
                .handlebars
                .register_template_string(&partial.name, content)
            {
                warn!(
                    component_id = %component.component_id(),
                    partial_name = %partial.name,
                    error = %e,
                    "Failed to compile partial template"
                );
            }
        }
    }

    async fn load_partial_file(&self, path: &Path) -> Result<String, TemplateError> {
        tokio::fs::read_to_string(path)
            .await
            .map_err(|e| TemplateError::LoadError {
                name: path.display().to_string(),
                message: e.to_string(),
            })
    }

    async fn load_template(
        &self,
        definition: &TemplateDefinition,
    ) -> Result<String, TemplateError> {
        for loader in &self.loaders {
            if loader.can_load(&definition.source) {
                return loader.load(&definition.source).await.map_err(|e| {
                    TemplateError::LoadError {
                        name: definition.name.clone(),
                        message: e.to_string(),
                    }
                });
            }
        }
        Err(TemplateError::NoLoader(definition.name.clone()))
    }
}