4 releases
Uses new Rust 2024
| new 0.0.4-alpha.1 | Nov 2, 2025 |
|---|---|
| 0.0.3 | Oct 12, 2025 |
| 0.0.2 | Oct 2, 2025 |
| 0.0.1 | Sep 29, 2025 |
#58 in Parser tooling
424 downloads per month
37KB
71 lines
templatia
A template-based serialization/deserialization library that enables seamless bidirectional conversion between Rust structs and text according to user-defined templates. This library is realized through two crates:
- templatia: A library providing core traits and errors
- templatia-derive: A macro library that automatically generates logic for bidirectional conversion between structs and text according to user templates
Typically, these are not used individually but combined via the derive feature of templatia.
(However, since templatia-derive currently only supports named_struct, custom implementations are also possible for special types.)
Features
- Alpha: Limited collection support added in 0.0.4-alpha.1 (
Vec<T>,HashSet<T>,BTreeSet<T>). See "Collection support (alpha)" for details. - Seamless bidirectional conversion between Rust structs and text
- Default template with all fields in key-value format:
{field_name} = {field_name} - Custom template definition using
templatiaattribute:#[templatia(template = "...")] - Clear runtime errors and understandable compile errors
- Examples of compile errors
- Compile error for consecutive ambiguous combinations
- StructName: Placeholder "field1" and "field2" are consecutive. These are ambiguous to parsing.
"field1" is
Stringtype data. Consecutive allows only: [char, bool]
- StructName: Placeholder "field1" and "field2" are consecutive. These are ambiguous to parsing.
"field1" is
- Compile error when not all struct fields are included in template placeholders
- StructName has more field specified than the template's placeholders: field1, field2, field3
If you want to allow missing placeholders, use
#[templatia(allow_missing_placeholders)]attribute.
- StructName has more field specified than the template's placeholders: field1, field2, field3
If you want to allow missing placeholders, use
- Compile error for consecutive ambiguous combinations
- Examples of compile errors
Minimum supported Rust version (MSRV)
- Rust 1.85.0
- Edition 2024
Installation
Using the cargo add command
cargo add templatia --features derive
Direct specification in Cargo.toml
- Import templatia. Add
deriveto features.
[dependencies]
templatia = { version = "0.0.3", features = ["derive"] }
use templatia::Template;
#[derive(Template)]
struct Config {
host: String,
port: u16,
}
fn main() {
let cfg = Config { host: "localhost".into(), port: 5432 };
let s = cfg.render_string();
assert!(s.contains("host = localhost"));
assert!(s.contains("port = 5432"));
}
Quick Start Guide
Default template
When no template is specified, each field is synthesized in the format field_name = {field_name}, one per line.
For example:
#[derive(Template)]
struct AwesomeStruct {
data1: String,
data2: u32,
}
fn main() {
let data = AwesomeStruct { data1: "data1".into(), data2: 100 };
}
In this case, the template is generated in the format:
data1 = {data1}
data2 = {data2}
When executing render_string(), you get the output:
data1 = data1
data2 = 100
Custom template
By using placeholders enclosed in {} with struct field names in the template within the templatia attribute, you can define a custom template.
In the following case, since "{host}:{port}" is defined, you can obtain db.example.com:3306 from cfg.
use templatia::Template;
#[derive(Template)]
#[templatia(template = "{host}:{port}")]
struct DbCfg {
host: String,
port: u16,
}
fn main() {
let cfg = DbCfg { host: "db.example.com".into(), port: 3306 };
assert_eq!(cfg.render_string(), "db.example.com:3306");
let parsed = DbCfg::from_str("db.example.com:3306").unwrap();
assert_eq!(parsed.host, "db.example.com");
assert_eq!(parsed.port, 3306);
}
Option support
Fields with Option<T> type automatically default to None when the placeholder is not present in the template:
use templatia::Template;
#[derive(Template)]
#[templatia(template = "host={host}:{port}", allow_missing_placeholders)]
struct ServerConfig {
host: String,
port: u16,
username: Option<String>,
password: Option<String>,
}
fn main() {
let config = ServerConfig::from_str("host=localhost:8080").unwrap();
assert_eq!(config.host, "localhost");
assert_eq!(config.port, 8080);
assert_eq!(config.username, None); // Not in template, defaults to None
assert_eq!(config.password, None); // Not in template, defaults to None
}
By default, empty strings in Option<String> are parsed as None. To treat empty strings as Some(""), use the empty_str_option_not_none attribute:
use templatia::Template;
#[derive(Template)]
#[templatia(template = "value={value}", empty_str_option_not_none)]
struct OptionalValue {
value: Option<String>,
}
fn main() {
let parsed = OptionalValue::from_str("value=").unwrap();
assert_eq!(parsed.value, Some("".to_string())); // Empty string becomes Some("")
}
Missing placeholders
Use the allow_missing_placeholders attribute to allow fields that are not present in the template:
use templatia::Template;
#[derive(Template)]
#[templatia(template = "id={id}", allow_missing_placeholders)]
struct Config {
id: u32,
name: String, // Not in template, uses Default::default()
optional: Option<u32>, // Not in template, becomes None
}
fn main() {
let config = Config::from_str("id=42").unwrap();
assert_eq!(config.id, 42);
assert_eq!(config.name, ""); // Default for String
assert_eq!(config.optional, None); // None for Option<T>
}
Placeholders and types
- Each
{name}in the template must correspond to a named struct field - Field types used in the template must implement Display and FromStr
- When
allow_missing_placeholdersis enabled, the Default trait implementation is also required.
- When
- It is possible to use placeholders for the same field multiple times within the template, but during from_str() the placeholders for the same field must have the same value.
- For example, if the template is
"{first_name} (Full: {first_name} {family_name})", you cannot deserializeTaro (Full: Jiro Yamada)into the struct.
- For example, if the template is
Collection support (alpha)
The alpha pre-release 0.0.4-alpha.1 introduces limited collection support in templates:
- Supported types:
Vec<T>,HashSet<T>,BTreeSet<T> - Representation: a single placeholder corresponds to a comma-separated list segment
- Example template:
items={items}matches inputs likeitems=a,b,c
- Example template:
- Empty segment means an empty collection (
items=) - Duplicate placeholders for the same field must have identical segment text
- Error reporting uses
TemplateError::ParseToTypewithtype_namelikeVec<u32>
Example:
use templatia::Template;
use std::collections::{HashSet, BTreeSet};
#[derive(Template)]
#[templatia(template = "items={items}; tags={tags}; ord={ord}")]
struct Data {
items: Vec<u32>,
tags: HashSet<String>,
ord: BTreeSet<i32>,
}
fn main() {
let d = Data::from_str("items=1,2,3; tags=red,blue,red; ord=1,1,2").unwrap();
assert_eq!(d.items, vec![1,2,3]);
assert_eq!(d.tags.len(), 2); // red, blue
assert_eq!(d.ord.len(), 2); // 1, 2
}
Notes:
- This is an alpha feature set prior to 0.0.4 GA. Behavior may change.
Runtime Errors
templatia defines a simple error type for parsing and validation:
- TemplateError::InconsistentValues { placeholder, first_value, second_value }
- Emitted when the same placeholder appears multiple times with conflicting parsed values
- TemplateError::ParseToType { placeholder, value, type_name }
- Parse error when the value cannot be parsed to the specified type
- TemplateError::UnexpectedInput { expected_next_literal, remaining_text }
- Input string literal does not match the specified template
- TemplateError::Parse(String)
- Generic parse error message
Crates overview
- templatia
- Template trait
- A trait that defines the behavior of
templatia. It defines two methods:render_string()andfrom_str(), and one associated type:Error.
- A trait that defines the behavior of
- TemplateError enum for error reporting
- Template trait
- templatia-derive
- #[derive(Template)] macro for named structs
- Optional attributes:
#[templatia(template = "...")]for custom templates#[templatia(allow_missing_placeholders)]to allow fields not in template#[templatia(empty_str_option_not_none)]to treat empty strings asSome("")forOption<String>
- Validates that placeholders exist as fields
Feature flags
- derive
- A flag that enables templatia-derive. By enabling this, you can derive
templatia::Template.
- A flag that enables templatia-derive. By enabling this, you can derive
Road Map (0.0.x roadmap toward 0.1.0)
- 0.0.2
- Define default behavior for missing data:
#[templatia(allow_missing_placeholders)]attribute allows fields not in template to useDefault::default() - Option: default to
Nonewhen the placeholder is absent (automatic support without requiringallow_missing_placeholders) - Remove
type StructfromTemplatetrait
- Define default behavior for missing data:
- 0.0.3
- Enrich error handling (clearer diagnostics and coverage through compile-fail tests)
- Internal refactoring to prepare for future feature implementations
- 0.0.4
- Declarative templates for field collections such as
Vec,HashMap, andHashSet - Add
containerattribute to increase flexibility at the parent structure level
- Declarative templates for field collections such as
- 0.0.5
- Support additional data forms: tuple (unnamed) structs, union structs, and enums
- 0.0.6 and beyond (Future versions)
- Optional placeholder syntax:
{name?}to make individual placeholders optional- For
Option<T>fields, treat the placeholder as empty string when value isNone - During parsing, return
Nonewhen the placeholder is absent
- For
- Range optional syntax:
[literal{placeholder}literal]?to make entire template sections optional- Example:
#[templatia(template = "[name={name}]?")]omitsname=entirely from output whennameisNone - Enables expressing presence/absence of optional sections while maintaining parse consistency
- Example:
- Optional placeholder syntax:
Testing policy and documentation conventions
This repository follows AGENTS.md for documentation and testing conventions. In short:
- Documentation comments are written in English
- Examples aim to be minimal, correct, and preferably compilable as doctests
- Tests should reflect intended behavior; do not change tests merely to match an implementation if they already reflect the documented intent
License
Dual-licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
You may use this software under the terms of either license.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Japanese README
Dependencies
~0.2–10MB
~70K SLoC