#template #serialization #deserialize #parser

templatia

A template library writedown and load the struct

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

Download history 308/week @ 2025-09-26 98/week @ 2025-10-03 196/week @ 2025-10-10 17/week @ 2025-10-17 2/week @ 2025-10-24

424 downloads per month

MIT or Apache-2.0

37KB
71 lines

CI Crates.io Docs.rs Crates.io MSRV (version) Downloads

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 templatia attribute: #[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 String type data. Consecutive allows only: [char, bool]
      • 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.

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

  1. Import templatia. Add derive to 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_placeholders is enabled, the Default trait implementation is also required.
  • 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 deserialize Taro (Full: Jiro Yamada) into the struct.

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 like items=a,b,c
  • Empty segment means an empty collection (items=)
  • Duplicate placeholders for the same field must have identical segment text
  • Error reporting uses TemplateError::ParseToType with type_name like Vec<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() and from_str(), and one associated type: Error.
    • TemplateError enum for error reporting
  • 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 as Some("") for Option<String>
    • Validates that placeholders exist as fields

Feature flags

  • derive
    • A flag that enables templatia-derive. By enabling this, you can derive templatia::Template.

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 use Default::default()
    • Option: default to None when the placeholder is absent (automatic support without requiring allow_missing_placeholders)
    • Remove type Struct from Template trait
  • 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, and HashSet
    • Add container attribute to increase flexibility at the parent structure level
  • 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 is None
      • During parsing, return None when the placeholder is absent
    • Range optional syntax: [literal{placeholder}literal]? to make entire template sections optional
      • Example: #[templatia(template = "[name={name}]?")] omits name= entirely from output when name is None
      • Enables expressing presence/absence of optional sections while maintaining parse consistency

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:

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

README_ja.md

Dependencies

~0.2–10MB
~70K SLoC