Skip to main content

confval v0.9.0

This release lets one language server process serve a configuration that spans several document shapes, lets a closed set of words apply to a list rather than only to a single value, and repairs a completion edit that could damage a list.

Highlights

Multi-document configuration

Some configurations span several document shapes: an entrypoint file and included files that carry their own root specs. Until now each shape needed its own server process and its own editor registration, which duplicated your file patterns into editor config.

serve_multi serves every shape from one process. You declare one binding per shape, pairing a matcher with the shape's root spec and its frontend. The server picks the schema per document when the editor opens it.

use confval_lsp::{Matcher, bind, serve_multi, Hcl};

serve_multi(vec![
bind::<EntrypointSpec, _>(Matcher::FileName("app.hcl".into()), Hcl),
bind::<MiddlewareSpec, _>(Matcher::Fn(Box::new(middleware_matcher)), Hcl),
])

Bindings are tried in declaration order, and the first match wins. A document that matches no binding stays open but inert, and one warning log names it. A matcher must not panic. On any problem, such as an unreadable file, answer no match. serve answers every request as it did, including for a never-saved buffer with no file path. Under serve, each opened document now produces a log message naming the binding it matched.

See the language server guide for the routing rules and the matcher contract.

A keyword set applies to a list

If a field accepts several words from a fixed vocabulary, you used to iterate the list yourself in a Validate implementation. #[confval(keywords = ...)] now applies to a string list in both shapes.

#[derive(confval::Spec)]
struct LoggingSpec {
#[confval(keywords = LogLevel)]
level: Located<String>,
#[confval(default, keywords = LogEvent)]
events: Vec<Located<String>>,
#[confval(keywords = LogPhase)]
phases: Option<Located<Vec<Located<String>>>>,
}

The derive runs the check once for each element and reports a bad entry at that entry's own span, so a diagnostic underlines the one word you need to change. The set also reaches the schema, so an editor offers the same completions inside the list that it offers on a scalar.

range stays on a scalar leaf, because a list of numbers is not a field shape confval parses. references stays on a scalar leaf, because the reference pass resolves one value against the labels in scope.

Added

  • confval_lsp::serve_multi, running one server process over a set of bindings.
  • confval_lsp::Router, the server behind both entry points. Build it with Router::new(bindings), which returns an error for an empty list, and run it over any lsp-server connection you already own.
  • confval_lsp::Binding and confval_lsp::bind, pairing a matcher with a root spec and a frontend.
  • confval_lsp::Matcher with the Any, FileName, and Fn variants.
  • confval_lsp::Validator, the validate pass for one root spec, built with Validator::of::<S>() and passed to handlers::diagnostics.
  • confval_lsp::LspError, the error serve, serve_multi, and Router return. It prints through Display, and every error type converts into it, so a function returning it propagates with the question mark.
  • The server logs routing through window/logMessage: the matched binding at LOG level, an unmatched document at WARNING.
  • #[confval(keywords = ...)] on Vec<Located<String>> and on Option<Located<Vec<Located<String>>>>.
  • KeywordSet::check_each_in, the check the derive emits for a list. Its message names the list, as in unknown value in modes: nope, and check_each keeps the singular wording for a handwritten caller.
  • SchemaType::scalar, SchemaType::string_list, SchemaType::block, and SchemaType::string_map, the constructors a handwritten ToSchema builds with.
  • SchemaType::constraint, which returns the constraint on a scalar or on a list's elements, so your own code renders a constraint without matching both variants.

Changed

  • confval_lsp::handlers::diagnostics takes a Validator instead of the root spec type parameter. Replace diagnostics::<S>(schema, ...) with diagnostics(Validator::of::<S>(), schema, ...). The function also computes the line index from the text it receives, so the index parameter is gone.
  • confval_lsp::serve requires F: Frontend + Send + 'static and S: 'static. The shipped frontends and every derived spec already satisfy the bounds, so only a handwritten frontend or spec type that borrows, or a frontend that is not Send, needs a change.
  • confval_lsp::Frontend has Debug as a supertrait. Add #[derive(Debug)] to a handwritten frontend.
  • LspError does not implement std::error::Error. A main that returns anyhow::Result<()> or Result<(), Box<dyn Error>> no longer accepts it. Return Result<(), confval_lsp::LspError>, or map it with anyhow::anyhow!(error).
  • SchemaType::StringList carries the constraint its elements declare.
  • SchemaType::StringList, SchemaType::Scalar, and SchemaType::Block are #[non_exhaustive]. A match arm on any of them takes a rest pattern, and a struct literal becomes a constructor call.
  • Constraint::Range and Constraint::References are #[non_exhaustive] too, and a constraint builds through Constraint::range, Constraint::keywords, and Constraint::references. Matching Constraint::Keywords(words) keeps working as it did.
  • handlers::ClientSupport and handlers::SymbolShape are #[non_exhaustive] and build through new, or through Default for ClientSupport.
  • The derive's messages for a misplaced constraint attribute are reworded. If you snapshot compiler errors, re-record those snapshots.
  • The confval init skills gained a starter-configuration step and a multi file section. Re-run the install on a project scaffolded at 0.8.0 to pick them up.
  • The minimum supported Rust version is 1.95. Run rustup update if your toolchain is older.

Removed

  • confval_lsp::Server<S, F>. serve keeps the single shape behavior. A host that owns its connection builds the router with one match-everything binding:
// Before
Server::<ServerSpec, Hcl>::new(Hcl).run(&connection)

// After
Router::new(vec![bind::<ServerSpec, Hcl>(Matcher::Any, Hcl)])?.run(&connection)

Fixed

  • Accepting a keyword completion inside a list replaced the whole list literal, which deleted the brackets and every other entry. The edit now replaces the one element under the cursor, inserts in an empty container or after a separator, and offers nothing at a position where the accepted text would not parse.
  • Hover inside a list element answered nothing. It now names the list field and its keyword set.
  • HCL reads and writes i64::MIN. Parsing -9223372036854775808 produces the value instead of an error, and emit_hcl writes it. This requires hcl-edit 0.9.7, which cargo update picks up. An HCL integer past the i64 range now reports oversized integer, the same diagnostic JSON, KDL, and YAML produce.

Upgrading

A single shape server that uses the shipped frontends and calls serve needs no code change. A handwritten frontend adds #[derive(Debug)], and one that borrows or is not Send no longer compiles. A direct handlers::diagnostics caller switches to Validator::of::<S>() and drops the line-index argument, which the function now computes from the text.

A handwritten ToSchema builds every SchemaType and Constraint through the constructors, and a match on SchemaType takes a rest pattern in each variant's arm. A derived spec needs neither change.

// Before
SchemaType::StringList
SchemaType::Block { schema: Box::new(inner), repeated: false }
SchemaType::Scalar { leaf, constraint } => ...
SchemaType::StringList => ...

// After
SchemaType::string_list(None)
SchemaType::block(inner, false)
SchemaType::Scalar { leaf, constraint, .. } => ...
SchemaType::StringList { .. } => ...
[dependencies]
confval = { version = "0.9", features = ["derive", "hcl", "color"] }