Back to Tensorzero

minijinja-utils

crates/minijinja-utils/README.md

2026.4.14.7 KB
Original Source

minijinja-utils

Static analysis utilities for MiniJinja templates.

Overview

This crate analyzes MiniJinja templates to extract all template dependencies by parsing the template syntax and walking the AST. It identifies include, import, from, and extends statements and determines whether template loading can be statically resolved.

Static vs Dynamic Template Loading

Static Loads ✓

Template names that can be determined at parse time:

jinja
{% include 'header.html' %}
{% extends 'base.html' %}
{% import 'macros.html' as m %}
{% include ['first.html', 'second.html'] %}
{% include 'a.html' if condition else 'b.html' %}
{% include 'optional.html' if condition %}

Dynamic Loads ✗

Template names that depend on runtime values:

jinja
{% include template_var %}
{% include get_template() %}

API

Main Function

rust
pub fn collect_all_template_paths(
    env: &Environment<'_>,
    template_name: &str,
) -> Result<HashSet<PathBuf>, AnalysisError>

Recursively collects all template paths starting from a root template. Returns an error if any dynamic loads are found or parsing fails.

Error Types

AnalysisError

rust
pub enum AnalysisError {
    ParseError(minijinja::Error),
    DynamicLoadsFound(Vec<DynamicLoadLocation>),
}
  • ParseError: Template has invalid MiniJinja syntax
  • DynamicLoadsFound: One or more templates contain dynamic loads that cannot be statically resolved

DynamicLoadLocation

Provides detailed location information for dynamic loads:

  • template_name: Which template contains the dynamic load
  • line, column: 1-indexed position in the template
  • span: Byte offsets of the expression
  • source_quote: Extracted source showing the problem
  • reason: Why it's dynamic (e.g., "variable", "conditional without else")
  • load_kind: Type of statement (include, import, etc.)

LoadKind

rust
pub enum LoadKind {
    Include { ignore_missing: bool },
    Import,
    FromImport,
    Extends,
}

Identifies which type of template loading statement was encountered.

Example Usage

rust
use minijinja::Environment;
use minijinja_utils::collect_all_template_paths;

let mut env = Environment::new();
env.add_template("main.html", "{% include 'header.html' %}Content").unwrap();
env.add_template("header.html", "Header").unwrap();

// Collect all template dependencies
let paths = collect_all_template_paths(&env, "main.html").unwrap();
assert_eq!(paths.len(), 2); // main.html and header.html

Error Handling

rust
use minijinja::Environment;
use minijinja_utils::{collect_all_template_paths, AnalysisError};

let mut env = Environment::new();
env.add_template("dynamic.html", "{% include template_var %}").unwrap();

match collect_all_template_paths(&env, "dynamic.html") {
    Err(AnalysisError::DynamicLoadsFound(locations)) => {
        for loc in locations {
            eprintln!("{}:{}:{}: {} - {}",
                loc.template_name, loc.line, loc.column,
                loc.load_kind, loc.reason);
        }
    }
    Err(AnalysisError::ParseError(e)) => {
        eprintln!("Parse error: {}", e);
    }
    Ok(paths) => {
        println!("Found {} templates", paths.len());
    }
}

Supported MiniJinja Features

  • Include: {% include 'template.html' %}
  • Import: {% import 'macros.html' as m %}
  • From Import: {% from 'macros.html' import button %}
  • Extends: {% extends 'base.html' %}
  • List includes: {% include ['a.html', 'b.html'] %}
  • Conditional with else: {% include 'a.html' if x else 'b.html' %}
  • Circular dependencies: Handled correctly without infinite loops

Limitations

  • Requires static template names: Cannot resolve variables, function calls, or expressions
  • Dependency on unstable API: Uses MiniJinja's unstable_machinery feature which may change between versions

Conditional Template Loading

When a template uses a conditional include without an else clause, the static template name is still extracted:

jinja
{% include 'optional.html' if show_feature %}

This will:

  • ✓ Extract 'optional.html' as a dependency
  • ✓ Require that 'optional.html' exists at analysis time
  • ⚠️ The template may or may not be loaded at runtime depending on the condition

This behavior enables static validation of all template paths while supporting conditional loading patterns. If the template name is dynamically computed (e.g., from a variable or function call), it will still be considered a dynamic load and produce an error.

Implementation Note

This crate uses MiniJinja's unstable_machinery feature to access the AST. This API is marked unstable and may change between MiniJinja versions.