tomlproc 0.1.0

A self-contained TOML 1.0.0 parser and serializer with no external dependencies
Documentation
  • Coverage
  • 100%
    41 out of 41 items documented5 out of 5 items with examples
  • Size
  • Source code size: 127.73 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 2.82 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 11s Average build duration of successful builds.
  • all releases: 7s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • KarpelesLab/tomlproc
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • MagicalTux

tomlproc

CI crates.io docs.rs License: MIT

A TOML 1.0.0 parser and serializer for Rust, with no dependencies — nothing outside the standard library, no build scripts, no proc macros, no unsafe.

The whole of TOML 1.0.0 is implemented: all four string flavours, all four date-time types, dotted keys, inline tables, arrays of tables, and the redefinition rules that decide which of those are legal together.

Why

Reading a config file should not pull a dependency tree into a project, and it should not cost a proc-macro crate's compile time. tomlproc is one small crate that parses a document into a plain value tree you index directly:

[dependencies]
tomlproc = "0.1"

Usage

let doc = tomlproc::parse(r#"
    title = "TOML Example"

    [owner]
    name = "Tom Preston-Werner"
    dob = 1979-05-27T07:32:00-08:00

    [[server]]
    ip = "10.0.0.1"
    ports = [8000, 8001]
"#)?;

assert_eq!(doc["title"].as_str(), Some("TOML Example"));
assert_eq!(doc["owner"]["dob"].as_datetime().unwrap().date.unwrap().year, 1979);
assert_eq!(doc["server"][0]["ports"][1].as_integer(), Some(8001));

// Or walk a path in one go, without unwrapping at each step.
assert_eq!(doc.get_path("server.0.ip").and_then(|v| v.as_str()), Some("10.0.0.1"));

Every accessor is type-strict, the way TOML is: as_float on an integer is None, not a conversion. get and get_path return an Option; indexing with [] panics on a missing key, like the standard collections.

Errors

Parse errors say where they are, in a line and a character column:

let error = tomlproc::parse("a = 1\nb = [1, 2").unwrap_err();
assert_eq!(error.line(), 2);
assert_eq!(
    error.to_string(),
    "TOML parse error at line 2, column 5: unterminated array",
);

Building and writing

Table is an insertion-ordered map, so a document keeps its key order from parse through to serialization:

let mut package = tomlproc::Table::new();
package.insert("name", "tomlproc");
package.insert("keywords", vec!["toml", "parser"]);

let mut doc = tomlproc::Table::new();
doc.insert("package", package);

assert_eq!(
    tomlproc::to_string(&doc),
    "[package]\nname = \"tomlproc\"\nkeywords = [\"toml\", \"parser\"]\n",
);

Sub-tables are written as [header] sections and arrays of tables as [[header]] sections, with plain keys always emitted ahead of the first header, so the output reads like a document a person would write.

Values round-trip: parse, serialize and re-parse gives an equal document. Formatting does not — comments, blank lines and the choice between a header and an inline table belong to the source text, not to the value model.

Conformance

The parser is strict. It rejects, with a position, everything the specification calls invalid, including:

  • duplicate keys, whether written bare, dotted or quoted;
  • redefining a table, or claiming a table that a dotted key created;
  • adding to an inline table after the fact;
  • mixing [table] and [[array of tables]] at the same name;
  • newlines or a trailing comma inside an inline table;
  • integers outside the range of a 64-bit signed integer, leading zeros, and misplaced underscores;
  • dates and times that do not exist (2023-02-29, 25:00:00);
  • control characters in strings and comments, and bare carriage returns.

Two places where the specification leaves a choice:

  • \r\n inside a multi-line string is normalized to \n, which the spec explicitly permits.
  • Fractional seconds beyond nanosecond precision are truncated, which the spec calls implementation-specific.

Conformance is checked by differential testing against the toml crate, the reference implementation, in tools/difftest: every .toml file in a corpus you point it at, plus 400,000 generated inputs — fragment soup aimed at the lexer's decision points, and whole statements aimed at the table redefinition rules. Both parsers must agree on what to accept, and on the value they produce. The current state is complete agreement, over ~1,700 real-world files and the generated set:

$ cargo run --release --manifest-path tools/difftest/Cargo.toml -- ~/.cargo/registry/src
1693 files + 400000 generated inputs
agree: 401693  different values: 0  only tomlproc accepts: 0  only the reference accepts: 0

That harness depends on the reference implementation, so it lives outside the crate: tomlproc itself still has no dependencies, and tools/ is excluded from the published package.

Arrays and inline tables may nest 128 deep. Parsing is recursive, so a document like a = [[[[… would otherwise exhaust the stack; the limit turns that into an ordinary parse error, which matters when the input is untrusted.

Types

Type What it is
Value A TOML value: string, integer, float, boolean, date-time, array or table
Table An insertion-ordered map of keys to values, with O(1) lookup
Datetime A date-time, in one of TOML's four flavours, via Date, Time and Offset
Error A parse failure, with a line, column and byte offset

Requirements

Rust 1.88 or newer, edition 2024.

License

MIT — see LICENSE.