Crate templatia

Crate templatia 

Source
Expand description

§Templatia

A powerful and easy-to-use library for converting Rust structs to and from text templates. Templatia lets you define templates with placeholders and automatically handles the serialization and deserialization, making configuration management and text processing effortless.

§Key Features

  • 🚀 Simple: Just add #[derive(Template)] to your struct
  • 🔧 Flexible: Use default templates or define custom ones
  • 🛡️ Safe: Type-safe parsing with clear error reporting
  • 🔄 Round-trip: Reliable conversion to/from strings

§Quick Start

Add templatia to your Cargo.toml:

[dependencies]
templatia = { version = "0.0.3", features = ["derive"] }

The easiest way to use templatia is with the derive macro for named structs:

use templatia::Template;

#[derive(Template)]
struct DatabaseConfig {
    host: String,
    port: u16,
    database: String,
}

let config = DatabaseConfig {
    host: "localhost".to_string(),
    port: 5432,
    database: "myapp".to_string(),
};

// Convert to template string (default format: field = {field})
let template = config.render_string();
assert_eq!(template, "host = localhost\nport = 5432\ndatabase = myapp");

// Parse back from template string
let parsed = DatabaseConfig::from_str(&template).unwrap();
assert_eq!(parsed.host, "localhost");
assert_eq!(parsed.port, 5432);

§Custom Templates

Define your own template format using the templatia attribute:

use templatia::Template;

#[derive(Template)]
#[templatia(template = "postgresql://{host}:{port}/{database}")]
struct PostgresUrl {
    host: String,
    port: u16,
    database: String,
}

let url = PostgresUrl {
    host: "db.example.com".to_string(),
    port: 5432,
    database: "production".to_string(),
};

assert_eq!(url.render_string(), "postgresql://db.example.com:5432/production");

let parsed = PostgresUrl::from_str("postgresql://localhost:5432/test").unwrap();
assert_eq!(parsed.database, "test");

§Advanced Features

§Duplicate Placeholders

Templatia supports duplicate placeholders as long as they have consistent values:

use templatia::Template;

#[derive(Template)]
#[templatia(template = "Welcome {name}! Your name is {name}.")]
struct Greeting {
    name: String,
}

let greeting = Greeting { name: "Alice".to_string() };
assert_eq!(greeting.render_string(), "Welcome Alice! Your name is Alice.");

// Parsing with inconsistent values will result in an error
let result = Greeting::from_str("Welcome Alice! Your name is Bob.");
assert!(result.is_err());
§Option<T> Support

Fields with Option<T> type automatically default to None when the placeholder is not 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>,
}

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:

use templatia::Template;

#[derive(Template)]
#[templatia(template = "value={value}")]
struct OptionalValue {
    value: Option<String>,
}

let parsed = OptionalValue::from_str("value=").unwrap();
assert_eq!(parsed.value, None); // Empty string becomes 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>,
}

let parsed = OptionalValue::from_str("value=").unwrap();
assert_eq!(parsed.value, Some("".to_string())); // Empty string becomes Some("")
§Missing Placeholders

Use allow_missing_placeholders to allow fields not 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
}

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>

§Manual Implementation (Advanced)

While the derive macro only supports named structs currently, you can manually implement the Template trait for other types like tuple structs, enums, or complex custom logic:

use templatia::{Template, TemplateError};

// Example: Tuple struct (derive doesn't support this yet)
struct Point(i32, i32);

impl Template for Point {
    type Error = TemplateError;

    fn render_string(&self) -> String {
        format!("({}, {})", self.0, self.1)
    }

    fn from_str(s: &str) -> Result<Self, Self::Error> {
        if !s.starts_with('(') || !s.ends_with(')') {
            return Err(TemplateError::Parse("Expected format: (x, y)".to_string()));
        }
         
        let inner = &s[1..s.len()-1];
        let parts: Vec<&str> = inner.split(", ").collect();
         
        if parts.len() != 2 {
            return Err(TemplateError::Parse("Expected two comma-separated values".to_string()));
        }
         
        let x = parts[0].parse().map_err(|_|
            TemplateError::Parse("Failed to parse x coordinate".to_string()))?;
        let y = parts[1].parse().map_err(|_|
            TemplateError::Parse("Failed to parse y coordinate".to_string()))?;
         
        Ok(Point(x, y))
    }
}

let point = Point(10, 20);
assert_eq!(point.render_string(), "(10, 20)");

let parsed = Point::from_str("(5, 15)").unwrap();
assert_eq!(parsed.0, 5);
assert_eq!(parsed.1, 15);

§Load Map (Roadmap)

Templatia follows a clear development roadmap with planned features:

§✅ Version 0.0.2 (Completed)

  • #[templatia(allow_missing_placeholders)] attribute: Fields not in template use Default::default()
  • Option<T> support: Automatically defaults to None when placeholder is absent
  • Empty string handling: By default, empty strings in Option<String> are parsed as None
  • #[templatia(empty_str_option_not_none)] attribute: Treats empty strings as Some("") instead of None
  • Removed type Struct associated type from Template trait (simplified to Self)
  • Bug fixes for consistent placeholder handling and parsing edge cases

§🔮 Version 0.0.3

  • Enriched error diagnostics and coverage
  • More detailed parsing error messages

§🎯 Version 0.0.4

  • Collections support: Vec, HashMap, HashSet
  • Container attributes for flexible parent structure configuration

§🌟 Version 0.0.5

  • Tuple struct support (derive macro)
  • Union struct support
  • Enum support

§Type Requirements

Fields used in templates must implement:

  • std::fmt::Display for serialization
  • std::str::FromStr for deserialization
  • std::cmp::PartialEq for consistency checks

Most common types (String, integers, floats, bool) implement these automatically.

§Error Handling

Templatia provides clear error types for different failure scenarios:

use templatia::{Template, TemplateError};

#[derive(Template)]
#[templatia(template = "port={port}")]
struct Config { port: u16 }

// Parse error example
match Config::from_str("port=not_a_number") {
    Err(TemplateError::ParseToType { placeholder, value, type_name }) => println!("Parse failed: placeholder '{}' with value '{}' could not be parsed as type '{}'", placeholder, value, type_name),
    _ => unreachable!(),
}

// Inconsistent values error (when same placeholder appears multiple times)
#[derive(Template)]
#[templatia(template = "id={id}-backup-{id}")]
struct BackupConfig { id: String }

match BackupConfig::from_str("id=prod-backup-dev") {
    Err(TemplateError::InconsistentValues { placeholder, first_value, second_value }) => {
        println!("Placeholder '{}' had conflicting values: '{}' vs '{}'",
                 placeholder, first_value, second_value);
    },
    _ => unreachable!(),
}

§Features

§derive

The derive feature enables the #[derive(Template)] procedural macro for automatic Template trait implementations on named structs.

When enabled, you can use:

[dependencies]
templatia = { version = "0.0.3", features = ["derive"] }

This feature is recommended for most users as it significantly simplifies usage:

  • Automatic trait implementation generation
  • Custom template support via #[templatia(template = "...")] attribute
  • Compile-time validation of templates and field references
  • Zero-cost abstractions with full type safety

Limitations: Currently only supports named structs. Tuple structs, unit structs, and enums require manual Template trait implementation.

For detailed usage examples, see the sections above.

Enums§

TemplateError
Errors produced by templatia operations.

Traits§

Template
A trait for converting between a struct and its string template form.