Skip to content

Commit c9f7ef4

Browse files
feat: add sha256, base64_encode, base64_decode MiniJinja template filters (#23) (#24)
1 parent d2758e5 commit c9f7ef4

9 files changed

Lines changed: 377 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Custom MiniJinja template filters: `sha256`, `base64_encode`, `base64_decode` available in all templates (render and seed spec files)
12+
- `sha256` filter with optional `mode` parameter (`"hex"` default, `"bytes"` for byte array output)
13+
- `base64_encode` / `base64_decode` filters for standard Base64 encoding and decoding with error handling for invalid input
14+
- Filters are chainable: e.g. `{{ "data" | sha256 | base64_encode }}`
15+
- `src/template_funcs.rs` dedicated module for template utility functions, designed for easy extension
16+
- `docs/templating.md` documenting all template filters with usage patterns, chaining examples, and error reference
17+
- `examples/template-functions/config.tmpl` example demonstrating sha256 and base64 filters
18+
- Unit tests for sha256 (hex, bytes, empty, invalid mode), base64 (encode, decode, roundtrip, invalid input), and template integration (filter chaining)
19+
1020
### Changed
1121
- Clarified that seed phases with only `create_if_missing` can omit the `seed_sets` field entirely (`seed_sets` defaults to empty via `#[serde(default)]`); updated integration test YAML specs accordingly
1222

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ rand = "0.8"
2424
ureq = { version = "2", features = ["tls"], default-features = false }
2525
rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] }
2626
minijinja = "2"
27+
sha2 = "0.10"
28+
base64 = "0.22"
2729
rusqlite = { version = "0.31", features = ["bundled"], optional = true }
2830
postgres = { version = "0.19", optional = true }
2931
mysql = { version = "25", optional = true }

docs/templating.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Template Functions
2+
3+
Initium extends the MiniJinja template engine with utility filters for hashing and encoding. These filters are available in all templates — both `render` templates and `seed` spec files.
4+
5+
## Available Filters
6+
7+
### `sha256`
8+
9+
Compute the SHA-256 hash of a string.
10+
11+
```jinja
12+
{{ "hello" | sha256 }}
13+
{# → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 #}
14+
```
15+
16+
**Parameters:**
17+
18+
| Parameter | Type | Default | Description |
19+
| --------- | ------ | ------- | ---------------------------------------- |
20+
| `mode` | string | `"hex"` | Output format: `"hex"` or `"bytes"` |
21+
22+
**Modes:**
23+
24+
- `"hex"` (default) — returns a lowercase hex string (64 characters).
25+
- `"bytes"` — returns an array of 32 byte values (integers 0–255).
26+
27+
```jinja
28+
{{ "hello" | sha256("hex") }}
29+
{# → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 #}
30+
31+
{{ "hello" | sha256("bytes") }}
32+
{# → [44, 242, 77, ...] (32 integers) #}
33+
```
34+
35+
### `base64_encode`
36+
37+
Encode a string to Base64 (standard alphabet with padding).
38+
39+
```jinja
40+
{{ "hello world" | base64_encode }}
41+
{# → aGVsbG8gd29ybGQ= #}
42+
```
43+
44+
### `base64_decode`
45+
46+
Decode a Base64 string back to its original value. Returns an error if the input is not valid Base64 or the decoded bytes are not valid UTF-8.
47+
48+
```jinja
49+
{{ "aGVsbG8gd29ybGQ=" | base64_decode }}
50+
{# → hello world #}
51+
```
52+
53+
## Chaining Filters
54+
55+
Filters can be chained to compose operations:
56+
57+
```jinja
58+
{# SHA-256 hash then Base64-encode the hex digest #}
59+
{{ "secret" | sha256 | base64_encode }}
60+
61+
{# Base64 encode then decode (roundtrip) #}
62+
{{ "data" | base64_encode | base64_decode }}
63+
64+
{# Hash an environment variable value #}
65+
{{ env.API_KEY | sha256 }}
66+
```
67+
68+
## Use Cases
69+
70+
### Content Fingerprinting
71+
72+
Generate a checksum for a config value to detect changes:
73+
74+
```jinja
75+
checksum: {{ env.CONFIG_DATA | sha256 }}
76+
```
77+
78+
### Encoding Secrets
79+
80+
Base64-encode a value for Kubernetes secret manifests:
81+
82+
```jinja
83+
data:
84+
password: {{ env.DB_PASSWORD | base64_encode }}
85+
```
86+
87+
### Verifying Integrity
88+
89+
Decode and verify Base64-encoded content:
90+
91+
```jinja
92+
decoded_cert: {{ env.B64_CERT | base64_decode }}
93+
```
94+
95+
## Error Handling
96+
97+
| Error | Cause |
98+
| -------------------------------- | --------------------------------------------------------- |
99+
| `sha256: unsupported mode '…'` | Mode parameter is not `"hex"` or `"bytes"` |
100+
| `base64_decode: invalid input` | Input string is not valid Base64 |
101+
| `base64_decode: not valid UTF-8` | Decoded bytes are not a valid UTF-8 string |
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{# Example: using sha256 and base64 template filters #}
2+
3+
[checksums]
4+
config_hash = {{ env.CONFIG_DATA | sha256 }}
5+
6+
[secrets]
7+
db_password = {{ env.DB_PASSWORD | base64_encode }}
8+
9+
[chained]
10+
password_hash_b64 = {{ env.DB_PASSWORD | sha256 | base64_encode }}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod render;
55
mod retry;
66
mod safety;
77
mod seed;
8+
mod template_funcs;
89

910
use clap::{Parser, Subcommand};
1011

src/render.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub fn template_render(input: &str) -> Result<String, String> {
6363
let env_map: std::collections::HashMap<String, String> = env::vars().collect();
6464
let mut jinja_env = minijinja::Environment::new();
6565
jinja_env.set_undefined_behavior(minijinja::UndefinedBehavior::Lenient);
66+
crate::template_funcs::register(&mut jinja_env);
6667
jinja_env
6768
.add_template("t", input)
6869
.map_err(|e| format!("parsing template: {}", e))?;

src/seed/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ fn render_template(content: &str) -> Result<String, String> {
88
let env_map: std::collections::HashMap<String, String> = std::env::vars().collect();
99
let mut jinja_env = minijinja::Environment::new();
1010
jinja_env.set_undefined_behavior(minijinja::UndefinedBehavior::Lenient);
11+
crate::template_funcs::register(&mut jinja_env);
1112
jinja_env
1213
.add_template("seed", content)
1314
.map_err(|e| format!("parsing seed template: {}", e))?;

0 commit comments

Comments
 (0)