# Elixir API Reference
Complete reference for the Mau template engine Elixir API.
## Overview
The Mau library provides a clean, functional API for template compilation and rendering. All functions use Elixir's standard error handling with `{:ok, result}` and `{:error, reason}` tuples.
## Main Module: Mau
The main entry point for all template operations.
### Mau.compile/2
Compiles a template string into an Abstract Syntax Tree (AST).
**Signature:**
```elixir
def compile(template, opts \\ []) :: {:ok, ast} | {:error, Error.t()}
```
**Parameters:**
- `template` (string) - Template source code to compile
- `opts` (keyword list, optional):
- `:strict_mode` - Enable strict error reporting (default: false)
- `:max_template_size` - Maximum allowed template size in bytes (default: 100,000)
**Return Values:**
- `{:ok, ast}` - Successfully compiled AST
- `{:error, error}` - Compilation error
**Examples:**
```elixir
# Simple compilation
{:ok, ast} = Mau.compile("Hello {{ name }}")
# With options
{:ok, ast} = Mau.compile(template, strict_mode: true)
# Error handling
case Mau.compile(invalid_template) do
{:ok, ast} -> IO.inspect(ast)
{:error, error} -> IO.puts("Compilation failed: #{error}")
end
```
**See Also:**
- [AST Specification](ast-specification.md) - Structure of compiled templates
---
### Mau.render/3
Renders a template string or pre-compiled AST with the given context.
**Signature:**
```elixir
def render(template, context, opts \\ []) :: {:ok, result} | {:error, Error.t()}
```
**Parameters:**
- `template` (string or AST) - Template source or compiled AST
- `context` (map) - Data context for variable substitution
- `opts` (keyword list, optional):
- `:preserve_types` - Preserve non-string types for single-value templates (default: false)
- `:max_template_size` - Maximum template size in bytes (default: 100,000)
- `:max_loop_iterations` - Maximum iterations in loops (default: 10,000)
**Return Values:**
- `{:ok, result}` - Rendered output (string, or original type with `preserve_types: true`)
- `{:error, error}` - Rendering error
**Examples:**
```elixir
# Basic rendering
{:ok, output} = Mau.render("Hello {{ name }}", %{"name" => "World"})
# Output: "Hello World"
# Rendering with pre-compiled AST
{:ok, ast} = Mau.compile(template)
{:ok, output} = Mau.render(ast, context)
# Preserving data types
{:ok, result} = Mau.render("{{ 42 }}", %{}, preserve_types: true)
# Result: 42 (integer, not "42" string)
# Boolean preservation
{:ok, result} = Mau.render("{{ user.active }}",
%{"user" => %{"active" => true}},
preserve_types: true)
# Result: true (boolean)
# Mixed content always returns string
{:ok, output} = Mau.render("Count: {{ items | length }}",
%{"items" => [1, 2, 3]},
preserve_types: true)
# Output: "Count: 3" (string)
# Error handling
case Mau.render(template, context) do
{:ok, output} -> IO.puts(output)
{:error, error} -> IO.puts("Render failed: #{error}")
end
# With options
{:ok, output} = Mau.render(template, context,
preserve_types: true,
max_loop_iterations: 5000)
```
**Behavior:**
- **Type Preservation**: When `preserve_types: true`:
- Single-value templates render to their native type (number, boolean, etc.)
- Mixed content (text + expressions) always renders to string
- Undefined variables return nil
- **Undefined Variables**: In strict mode (false by default):
- Undefined variables render as empty strings
- No error is raised
**See Also:**
- [Template Language Reference](template-language.md) - Template syntax
- [Filters List](filters-list.md) - Available filters
---
### Mau.render_map/3
Recursively renders template strings in nested map structures with support for transformation directives.
**Signature:**
```elixir
def render_map(nested_map, context, opts \\ []) :: {:ok, result} | {:error, Error.t()}
```
**Parameters:**
- `nested_map` (map) - Nested map structure containing template strings
- `context` (map) - Data context for variable substitution
- `opts` (keyword list, optional):
- `:preserve_types` - Preserve data types in results (default: true)
- `:max_template_size` - Maximum template size (default: 100,000)
- `:max_loop_iterations` - Maximum loop iterations (default: 10,000)
**Return Values:**
- `{:ok, result}` - Rendered map with all template strings processed
- `{:error, error}` - Rendering error
**Directives:**
Map keys starting with `#` trigger transformation directives:
| Directive | Purpose | Syntax |
|-----------|---------|--------|
| `#map` | Iterate over collections | `"#map" => [collection, template]` |
| `#filter` | Filter collections | `"#filter" => [collection, condition]` |
| `#merge` | Combine maps | `"#merge" => [map1, map2, ...]` |
| `#if` | Conditional rendering | `"#if" => [condition, true_tmpl, false_tmpl]` |
| `#pick` | Extract specific keys | `"#pick" => [map, key_list]` |
| `#pipe` | Thread through transformations | `"#pipe" => [initial, directives]` |
**Examples:**
```elixir
# Simple map rendering
input = %{
"greeting" => "Hello {{ name }}!",
"count" => "{{ items | length }}"
}
context = %{"name" => "Alice", "items" => [1, 2, 3]}
{:ok, result} = Mau.render_map(input, context)
# Result: %{
# "greeting" => "Hello Alice!",
# "count" => "3"
# }
# Using #map directive
input = %{
"users" => %{
"#map" => [
"{{$users}}",
%{"name" => "{{$loop.item.name}}"}
]
}
}
context = %{
"$users" => [
%{"name" => "Alice"},
%{"name" => "Bob"}
]
}
{:ok, result} = Mau.render_map(input, context)
# Using #filter directive
input = %{
"active_users" => %{
"#filter" => [
"{{$users}}",
"{{$loop.item.active}}"
]
}
}
# Using #merge directive
input = %{
"profile" => %{
"#merge" => [
"{{$user}}",
%{"verified" => true}
]
}
}
# Using #if directive
input = %{
"status" => %{
"#if" => [
"{{$premium}}",
%{"level" => "premium"},
%{"level" => "free"}
]
}
}
# Using #pipe for data transformation
input = %{
"result" => %{
"#pipe" => [
"{{$items}}",
[
%{"#filter" => "{{$loop.item.price > 100}}"},
%{"#map" => %{"name" => "{{$loop.item.name}}"}}
]
]
}
}
# Error handling
case Mau.render_map(nested_data, context) do
{:ok, result} -> IO.inspect(result)
{:error, error} -> IO.puts("Map render failed: #{error}")
end
```
**Context Variables in Directives:**
Special variables available in template strings within directives:
```
{{$self}} # The piped value (in #pipe directive)
{{$loop.item}} # Current item in #map or #filter
{{$loop.index}} # Current item index (0-based)
{{$loop.first}} # Is first item? (boolean)
{{$loop.parentloop}} # Parent loop info (in nested loops)
```
**See Also:**
- [Map Directives Reference](map-directives.md) - Complete directive documentation
---
## Supporting Modules
### Mau.Error
Handles error types and messages.
**Functions:**
#### Mau.Error.runtime_error/1
Creates a runtime error with a custom message.
```elixir
Mau.Error.runtime_error("Template error message")
```
---
### Mau.FilterRegistry
Manages available filters (functions).
**Functions:**
#### Mau.FilterRegistry.apply/3
Applies a filter to a value.
**Signature:**
```elixir
def apply(filter_name, value, args \\ []) ::
{:ok, result} | {:error, :filter_not_found | {:filter_error, reason}}
```
**Examples:**
```elixir
# Apply a simple filter
{:ok, result} = Mau.FilterRegistry.apply("upper_case", "hello", [])
# Result: "HELLO"
# Apply filter with arguments
{:ok, result} = Mau.FilterRegistry.apply("truncate", "Hello World", [8])
# Result: "Hello..."
# Error handling
case Mau.FilterRegistry.apply("upper_case", 123, []) do
{:ok, result} -> IO.puts(result)
{:error, :filter_not_found} -> IO.puts("Filter not found")
{:error, {:filter_error, reason}} -> IO.puts("Filter error: #{reason}")
end
```
**Available Filters:**
All filters from the three filter modules:
- String filters: 6 filters
- Collection filters: 18 filters
- Math filters: 10 filters
See [Filters List](filters-list.md) for complete reference.
---
#### Mau.FilterRegistry.get/1
Gets a filter function by name.
**Signature:**
```elixir
def get(filter_name) :: {:ok, {module, function}} | {:error, :not_found}
```
**Examples:**
```elixir
# Get filter function
{:ok, {module, function}} = Mau.FilterRegistry.get("upper_case")
# Error handling
case Mau.FilterRegistry.get("upper_case") do
{:ok, {mod, func}} -> :io.format("Filter: ~w:~w~n", [mod, func])
{:error, :not_found} -> IO.puts("Filter not found")
end
```
---
### Mau.MapDirectives
Handles transformation directives in maps.
**Functions:**
#### Mau.MapDirectives.match_directive/1
Identifies if a map contains a supported directive.
**Signature:**
```elixir
def match_directive(map) :: {directive_type, args} | :none
```
**Examples:**
```elixir
# Check for directive
case Mau.MapDirectives.match_directive(%{"#map" => [collection, template]}) do
{:map, args} -> IO.inspect(args)
:none -> IO.puts("Not a directive")
end
```
---
#### Mau.MapDirectives.apply_directive/4
Applies a directive to transform data.
**Signature:**
```elixir
def apply_directive(directive, context, opts, render_fn) :: result
```
**Directives:**
- `:map` - Iterate over collection
- `:filter` - Filter collection items
- `:merge` - Merge maps
- `:if` - Conditional rendering
- `:pick` - Extract keys
- `:pipe` - Pipeline transformations
---
## Data Types
### AST (Abstract Syntax Tree)
Templates compile to AST represented as tuples.
**Node Structure:**
```elixir
{node_type, content, options}
```
**Node Types:**
- `:text` - Literal text content
- `:expression` - Variable interpolation
- `:tag` - Control flow (if/for)
- `:literal` - Constant values
- `:variable` - Variable reference
- `:binary_op` - Binary operations
- `:logical_op` - Logical operations
- `:call` - Function/filter calls
**Example:**
```elixir
{:ok, ast} = Mau.compile("Hello {{ name | upper_case }}")
# AST structure:
# [
# {:text, ["Hello "], []},
# {:expression,
# {:call, ["upper_case",
# {:variable, ["name"], []}
# ], []},
# []}
# ]
```
**See Also:**
- [AST Specification](ast-specification.md) - Complete AST documentation
---
### Context Map
The context is a standard Elixir map containing variables for template rendering.
**Structure:**
```elixir
%{
"variable_name" => value,
"user" => %{
"name" => "Alice",
"email" => "alice@example.com"
},
"$workflow_var" => workflow_value
}
```
**Variable Naming:**
- Regular variables: `"name"`, `"user"`, etc.
- Workflow variables: `"$input"`, `"$nodes"`, `"$variables"`, `"$context"`
- Both strings and atoms are supported as keys
**Examples:**
```elixir
# Simple context
context = %{"name" => "Alice", "age" => 30}
# Nested context
context = %{
"user" => %{
"name" => "Bob",
"profile" => %{
"bio" => "Developer"
}
}
}
# With workflow variables
context = %{
"$input" => %{"email" => "user@example.com"},
"$nodes" => %{
"fetch_user" => %{"output" => user_data}
},
"$variables" => %{"api_key" => "secret"}
}
```
---
## Error Handling
All Mau functions follow Elixir's standard error handling pattern.
**Error Structure:**
```elixir
{:error, error_message}
```
**Error Types:**
- Compilation errors (invalid template syntax)
- Runtime errors (undefined variables, filter errors)
- Type errors (filter applied to wrong type)
- Size errors (template exceeds max_template_size)
- Loop limit errors (exceeds max_loop_iterations)
**Example Error Handling:**
```elixir
case Mau.render(template, context) do
{:ok, output} ->
IO.puts("Success: #{output}")
{:error, error} ->
IO.puts("Error: #{error}")
# Log error, send to error tracking, etc.
end
# With rescue for unexpected errors
try do
{:ok, result} = Mau.render(template, context)
result
rescue
e ->
IO.puts("Unexpected error: #{Exception.message(e)}")
raise e
end
```
---
## Options Reference
### Mau.compile/2 Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `strict_mode` | boolean | false | Enable strict error reporting |
| `max_template_size` | integer | 100,000 | Maximum template size in bytes |
### Mau.render/3 Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `preserve_types` | boolean | false | Preserve non-string types for single values |
| `max_template_size` | integer | 100,000 | Maximum template size in bytes |
| `max_loop_iterations` | integer | 10,000 | Maximum iterations in loops |
### Mau.render_map/3 Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `preserve_types` | boolean | true | Preserve non-string types in results |
| `max_template_size` | integer | 100,000 | Maximum template size in bytes |
| `max_loop_iterations` | integer | 10,000 | Maximum iterations in loops |
---
## Common Patterns
### Compile Once, Render Many
For performance, compile templates once and reuse the AST:
```elixir
# Compile once
{:ok, template_ast} = Mau.compile(template_string)
# Render multiple times with different contexts
contexts = [
%{"name" => "Alice"},
%{"name" => "Bob"},
%{"name" => "Charlie"}
]
results = Enum.map(contexts, fn context ->
case Mau.render(template_ast, context) do
{:ok, output} -> output
{:error, _} -> nil
end
end)
```
### Pipeline Processing
Render templates as part of data processing pipelines:
```elixir
data
|> Enum.map(&preprocess/1)
|> Enum.map(&render_with_context/1)
|> Enum.filter(&valid?/1)
|> Enum.map(&postprocess/1)
defp render_with_context(data) do
case Mau.render(data.template, data.context) do
{:ok, output} -> %{data | output: output}
{:error, error} -> %{data | error: error}
end
end
```
### Type-Safe Data Transformation
Use `preserve_types: true` for type-safe transformations:
```elixir
# Extract numeric values from templates
{:ok, price} = Mau.render("{{ product.price }}",
%{"product" => %{"price" => 29.99}},
preserve_types: true)
# Safely use as number
total = price * quantity # No conversion needed
# Extract booleans
{:ok, is_active} = Mau.render("{{ user.active }}",
context,
preserve_types: true)
if is_active do
# ...
end
```
---
## Performance Considerations
### Template Compilation
- **Compile once, render many times** - Compilation is the expensive operation
- **Reuse AST** - Pass compiled AST to render instead of template string
- **Cache compiled templates** - Store AST in ETS or application state
### Filter Performance
- **Filters are optimized** - Implemented in native Elixir
- **Avoid long chains** - Chain filters efficiently but readably
- **Consider custom filters** - For domain-specific high-performance transformations
### Loop Performance
- **Set `max_loop_iterations`** - Prevent runaway loops in user input
- **Use `#pipe` for complex transforms** - More efficient than nested `#map` directives
- **Profile with `:benchee`** - Measure performance of templates
---
## Version Compatibility
- **Elixir**: 1.12+
- **Erlang**: 23+
- **OTP**: 23+
---
## See Also
- [Template Language Reference](template-language.md) - Complete syntax documentation
- [Filters List](filters-list.md) - All available filters
- [Map Directives](map-directives.md) - Directive system reference
- [AST Specification](ast-specification.md) - AST structure details
- [Filters Guide](../guides/filters.md) - How to use filters effectively
- [Custom Filters](../advanced/custom-filters.md) - Extend with custom filters