greentic-flow-builder 0.1.0

AI-powered Adaptive Card flow builder with visual graph editor and demo runner
Documentation
//! Load built-in templates from the compile-time embedded blob.

use super::RegistryBase;
use crate::template::{Catalog, PresetInfo, PrimitivesManifest, Theme};
use crate::theme::ThemeRegistry;
use anyhow::Context;
use handlebars::Handlebars;
use serde_json::Value;
use std::collections::BTreeMap;

const BUILT_IN_BUNDLE: &str = include_str!(concat!(env!("OUT_DIR"), "/embedded_templates.json"));

pub fn load_builtin() -> anyhow::Result<RegistryBase> {
    let bundle: Value =
        serde_json::from_str(BUILT_IN_BUNDLE).context("parsing embedded templates bundle")?;

    let mut handlebars = Handlebars::new();
    handlebars.set_strict_mode(false);
    crate::render::helpers::register_all(&mut handlebars);

    register_primitives(&mut handlebars, &bundle)?;
    let presets = load_presets(&mut handlebars, &bundle)?;
    let themes = load_themes(&bundle)?;
    let primitives_manifest = load_primitives_manifest(&bundle)?;
    let categories = load_catalog(&bundle)?;

    Ok(RegistryBase {
        handlebars,
        presets,
        themes,
        primitives_manifest,
        categories,
    })
}

fn register_primitives(hbs: &mut Handlebars<'static>, bundle: &Value) -> anyhow::Result<()> {
    let primitives = bundle
        .get("primitives")
        .and_then(|v| v.as_object())
        .context("bundle missing 'primitives' object")?;

    for (name, source_val) in primitives {
        let source = source_val
            .as_str()
            .context("primitive source must be string")?;
        hbs.register_partial(name, source)
            .with_context(|| format!("registering primitive partial '{}'", name))?;
    }
    Ok(())
}

fn load_presets(
    hbs: &mut Handlebars<'static>,
    bundle: &Value,
) -> anyhow::Result<BTreeMap<String, PresetInfo>> {
    let presets = bundle
        .get("presets")
        .and_then(|v| v.as_object())
        .context("bundle missing 'presets' object")?;

    let mut map = BTreeMap::new();
    for (name, preset_data) in presets {
        let source = preset_data["source"]
            .as_str()
            .context("preset source missing")?;
        let schema = preset_data["schema"].clone();

        hbs.register_template_string(name, source)
            .with_context(|| format!("registering preset template '{}'", name))?;

        let info = PresetInfo {
            name: name.clone(),
            category: schema["x-category"].as_str().unwrap_or("").to_string(),
            title: schema["title"].as_str().unwrap_or(name).to_string(),
            description: schema["description"].as_str().unwrap_or("").to_string(),
            tags: schema["x-tags"]
                .as_array()
                .map(|arr| {
                    arr.iter()
                        .filter_map(|v| v.as_str().map(String::from))
                        .collect()
                })
                .unwrap_or_default(),
            use_cases: schema["x-use-cases"]
                .as_array()
                .map(|arr| {
                    arr.iter()
                        .filter_map(|v| v.as_str().map(String::from))
                        .collect()
                })
                .unwrap_or_default(),
            example: schema.get("x-example").cloned().unwrap_or(Value::Null),
            schema,
            template_source: source.to_string(),
        };
        map.insert(name.clone(), info);
    }
    Ok(map)
}

fn load_themes(bundle: &Value) -> anyhow::Result<ThemeRegistry> {
    let themes_obj = bundle
        .get("themes")
        .and_then(|v| v.as_object())
        .context("bundle missing 'themes' object")?;

    let mut registry = ThemeRegistry::new();
    for (name, theme_json) in themes_obj {
        let theme: Theme = serde_json::from_value(theme_json.clone())
            .with_context(|| format!("parsing theme '{}'", name))?;
        registry.insert(theme);
    }
    Ok(registry)
}

fn load_primitives_manifest(bundle: &Value) -> anyhow::Result<PrimitivesManifest> {
    let manifest_json = bundle
        .get("primitives_manifest")
        .cloned()
        .context("bundle missing 'primitives_manifest'")?;
    serde_json::from_value(manifest_json).context("parsing primitives manifest")
}

fn load_catalog(bundle: &Value) -> anyhow::Result<Vec<crate::template::Category>> {
    let catalog_json = bundle
        .get("catalog")
        .cloned()
        .context("bundle missing 'catalog'")?;
    let catalog: Catalog = serde_json::from_value(catalog_json).context("parsing catalog")?;
    Ok(catalog.categories)
}