confval v0.7.0
This release adds a KDL frontend, three ways to print what a service loaded, and commented-out entries so a generated template shows you every setting a spec accepts rather than only the ones that carry a value.
Highlights
KDL joins HCL and TOML
If you write configuration in KDL, confval now parses and emits it, behind a kdl feature.
hostname "127.0.0.1"
port 8443
allow "10.0.0.0/8" "192.168.0.0/16"
limits {
mode "log"
}
A node with only arguments is a value.
A node with properties or children is a block, so tls cert="a.pem" and tls { cert "a.pem" } describe the same
block.
Everything after the parse call is unchanged.
The hcl, toml, and kdl examples run the same steps in the same order, and only the source text and the two
format calls that parse and emit it differ.
See Parsing a file for the entry points, and Examples for the three side by side.
Three representations from one loaded spec
When someone asks what a service is running, there are three useful answers. confval now produces all three.
The source view, to_source_fields, is the configuration as it was written, with defaults left out.
It answers what was set in the file.
Each value it keeps carries the span it came from, so a tool can still report where a value was written.
The populated view, to_fields, fills every default and answers what the service resolved to.
The runtime view is ordinary serde on the lowered config and answers what is running.
All three render through the existing emitters, so you can print any of them in any format.
See Representations for when to use each one and for the list shape that cannot keep the distinction.
Templates show the settings you have not set
An absent optional field used to vanish from a generated template, doc comment included, so the template never told you
the setting existed.
to_template now renders each one as a commented-out entry with its doc comment above it.
TOML and HCL prefix every line of the entry with a # and no space, so enabling a setting means removing that
character from each of the entry's lines.
# The PID file path.
#pid_file = ""
KDL prefixes the node with /-, its own marker for a node the parser skips, so one deletion enables a whole block.
Every parser ignores a commented entry, so a template describes the same configuration whether you enable a setting or
not.
The plain dump, to_fields, emits none of them.
See Commented-Out Entries for the placeholder each shape renders.
Added
- The
kdlfeature, withparse_kdl,parse_kdl_fields, andemit_kdl. - Commented-out entries in
to_template, along with theEntrytype,Field::as_commented,Fields::from_entries,Fields::detached_entries, andFields::entriesfor a handwritten template walk. ToFields::to_source_fields, the source view described above.ToFields::spec_docandToFields::type_doc, and a struct-level#[confval(doc = "...")]. A template now takes a field's comment from the field'sdocattribute, then the field's rustdoc, then the embedded type's own doc, so a spec documented once at its definition annotates every site that embeds it.FieldsBuilder,Walk, and the sealedLeaftrait, for writing aToFieldsby hand without listing the fields twice. See Writing emitters by hand.Field::parsed, the constructor a format frontend builds its fields with, andField::at, which locates a constructed field.Value::spanned, the span-carrying value constructor.parse_path_field, which reads aLocated<PathBuf>field. The derive now generates this call for that shape.first_occurrenceandparse_string_list_occurrence, the helpers behind the repeated-field handling described under Changed.KeywordSet::check_each, andnarrow::keyword_listandnarrow::opt_keyword_list, for keyword list fields on the validation side and the lowering side.keyword_enum!now generates aserde::Serializeimpl behind theserdefeature, writing the keyword string so a serialized config spells"log"rather than the Rust variant name.
Changed
- A repeated single-value field now reports a duplicate at the second occurrence and keeps the first value.
The diagnostic's related span points at the first occurrence.
It previously kept the last one silently.
A list field accumulates its occurrences in document order.
You reach this through KDL, where repeated nodes are the natural way to spell a list, or through a
Fieldsvalue you build yourself. HCL and TOML reject a repeated key in their own parsers, so a file in either format is unaffected. parse_string_list_fieldaccepts a lone string as a one-element list, because a format with no array literal spells a one-element list as a single value. A loneScalar::Unparsed, the kind an environment variable or a flag yields, stays a type mismatch.emit_hclwrites values before blocks at each level, and puts a blank line above every block that follows another structure. This is the Terraform convention, and it matches the order TOML's syntax forces. It applies to everyemit_hclcall, not to templates alone.- A level now holds
Entryvalues rather thanFieldvalues, which is what carries the commented marker.Fields::iter,get,has, and the generated parse walk yield the fields a configuration sets, and never a commented entry.Fields::entriesyields every entry and is what the emitters consume. Fieldis now#[non_exhaustive].
Upgrading
If you are upgrading from 0.6, four changes can break your build. Each one below names what to change.
ToFields gains a required method, to_source_fields.
If you derived your spec with #[derive(Spec)], the derive generates the method and you have nothing to change.
If you wrote a ToFields impl by hand, add the method and return the fields the source set.
Build both walks with FieldsBuilder, which takes the walk as a parameter and omits a field with a detached span from
the source walk, so you list your fields once rather than writing the span checks twice.
See Writing emitters by hand for the full impl.
Field is #[non_exhaustive], so a struct literal no longer compiles.
You hit this if you maintain a format frontend outside confval that builds its own fields.
Use Field::parsed on the read path, or Field::detached_value and Field::detached_block on the write path, then
attach what the shape needs through with_doc and at.
With the marker in place, the next field added to Field is a minor release rather than another break.
Enabling the serde feature adds a Serialize impl to every keyword_enum! type.
If you wrote your own Serialize for such an enum, remove it.
Watch for this even if you did not enable serde yourself.
Cargo unifies features across the dependency graph, so another crate turning on confval's serde turns this impl on
for you.
The conflict then appears as an error in your own code.
Field no longer carries a commented flag, and Field::as_commented returns an Entry.
You hit this if you wrote a to_template by hand that marks its own commented entries, or if you read
field.commented anywhere.
Build a level with Fields::detached_entries or Fields::from_entries, whose items are Entry values, and convert an
active field with .into().
Read the marker through Fields::entries and Entry::is_commented.
For example, a level with one active entry and one commented entry is built like this:
Fields::detached_entries(vec![
Field::detached_value("port", port_value).into(),
Field::detached_value("pid_file", placeholder).as_commented(),
])
Update the dependency and enable the features you use.
Add kdl only if you parse that format.
[dependencies]
confval = { version = "0.7.0", features = ["derive", "toml", "hcl", "kdl", "color", "layering"] }