TOML

facet-toml provides TOML serialization and deserialization, built on toml_edit. Perfect for configuration files.

Basic usage

use facet::Facet;

#[derive(Facet)]
struct Config {
    name: String,
    version: String,
    debug: bool,
}

// Deserialize
let toml = r#"
name = "myapp"
version = "1.0.0"
debug = true
"#;
let config: Config = facet_toml::from_str(toml)?;

// Serialize
let output = facet_toml::to_string(&config)?;

Nested tables

TOML tables map to nested structs:

#[derive(Facet)]
struct Package {
    name: String,
    version: String,
}

#[derive(Facet)]
struct Server {
    host: String,
    port: u16,
}

#[derive(Facet)]
struct Config {
    package: Package,
    server: Server,
}

let toml = r#"
[package]
name = "myapp"
version = "1.0.0"

[server]
host = "localhost"
port = 8080
"#;

let config: Config = facet_toml::from_str(toml)?;

Arrays

TOML arrays work with Vec<T>:

#[derive(Facet)]
struct Dependency {
    name: String,
    version: String,
}

#[derive(Facet)]
struct Config {
    dependencies: Vec<Dependency>,
}

let toml = r#"
[[dependencies]]
name = "serde"
version = "1.0"

[[dependencies]]
name = "tokio"
version = "1.0"
"#;

let config: Config = facet_toml::from_str(toml)?;
assert_eq!(config.dependencies.len(), 2);

Inline tables

Inline tables in TOML are handled automatically:

# Both forms work:
server = { host = "localhost", port = 8080 }

# Or:
[server]
host = "localhost"
port = 8080

Optional fields

Use Option<T> for optional configuration:

#[derive(Facet)]
struct Database {
    host: String,
    port: Option<u16>,  // Optional in TOML
    #[facet(default = 10)]
    pool_size: u32,     // Defaults to 10 if missing
}

DateTime support

TOML has native datetime support. Enable the appropriate feature:

[dependencies]
facet = { version = "{{ data.versions.facet }}", features = ["chrono"] }
# or "time" or "jiff02"
use chrono::{DateTime, Utc};

#[derive(Facet)]
struct Event {
    name: String,
    timestamp: DateTime<Utc>,
}

let toml = r#"
name = "deploy"
timestamp = 2024-01-15T10:30:00Z
"#;

let event: Event = facet_toml::from_str(toml)?;

Error messages

facet-toml provides helpful error messages with source locations:

Error: missing field `port`
  ┌─ config.toml:2:1
  │
2 │ [server]
  │ ^^^^^^^^ expected field `port` here
  │

Common patterns

Cargo.toml-style configs

#[derive(Facet)]
struct Manifest {
    package: Package,
    #[facet(default)]
    dependencies: HashMap<String, Dependency>,
}

#[derive(Facet)]
struct Package {
    name: String,
    version: String,
    #[facet(default)]
    authors: Vec<String>,
}

#[derive(Facet)]
#[facet(untagged)]
enum Dependency {
    Simple(String),  // "1.0"
    Detailed {
        version: String,
        #[facet(default)]
        features: Vec<String>,
    },
}

Environment-specific configs

#[derive(Facet)]
struct Config {
    #[facet(default)]
    development: Option<ServerConfig>,
    #[facet(default)]
    production: Option<ServerConfig>,
}

#[derive(Facet)]
struct ServerConfig {
    host: String,
    port: u16,
    #[facet(default)]
    tls: bool,
}

Serialization

Serialize to TOML:

let config = Config {
    package: Package {
        name: "myapp".into(),
        version: "1.0.0".into(),
    },
    server: Server {
        host: "localhost".into(),
        port: 8080,
    },
};

let toml = facet_toml::to_string(&config)?;
println!("{}", toml);

Output:

[package]
name = "myapp"
version = "1.0.0"

[server]
host = "localhost"
port = 8080

Next steps