Skip to content

A configuration language

Typed configuration that always finishes.

Stav is a statically typed, eager and total configuration language with block syntax. It checks the whole program before it runs any of it, merges what every file contributes by priority, and renders the result to JSON, TOML or YAML.

cargo install stav-cli

app.stav
stav
type Level = debug | info | warn | error

struct Upstream {
  url:    Str where matches(r"https?://.*")
  weight: Int where 1..=100 = 10
}

struct Root {
  listen:    Str = "127.0.0.1:8080"
  log_level: Level = info
  upstreams: Map<Str, Upstream> = {} block upstream
}

log_level = warn

upstream primary { url = "https://a.example.org", weight = 90 }
upstream backup { url = "https://b.example.org" }
toml
listen = "127.0.0.1:8080"
log_level = "warn"

[upstreams.backup]
url = "https://b.example.org"
weight = 10

[upstreams.primary]
url = "https://a.example.org"
weight = 90

Six properties, held by the language

These are not conventions to follow. The checker and the evaluator enforce them for every program.

  • Typed

    The whole program is type checked before any of it runs. Unknown names, broken imports and wrong types are reported up front, with the closest valid name as a hint.

    weight: Int where 1..=100
  • Total

    Evaluation always ends. Functions cannot call themselves, types cannot be recursive, loops run over finite collections, and fuel limits stop runaway growth.

    fold(range(1, 6), 1, f)
  • Eager

    Statements run in order, once. The only deferred value is a late-bound self, read after merging along a graph that is checked to be acyclic.

    url = "#{self.version}"
  • Pure

    Evaluation reads nothing but the source files it is given. No environment, no clock, no network. The same inputs give the same value, byte for byte.

    stav eval --canonical
  • Merging

    Every file contributes to one value. Lists concatenate, maps merge per key, and single values settle by priority, from declared default up to force.

    port = force(443)
  • Parts

    Typed fragments of a body, written as functions or files. Pass parameters in, then include them wherever their struct fits.

    include web("api")

From source files to one value

  1. Check

    Every file is parsed and the whole program is type checked against its root struct.

  2. Evaluate

    Statements run eagerly, in order, and each one contributes values at a path and a priority.

  3. Merge

    Contributions meet by path. The highest priority wins, collections combine, conflicts are reported.

  4. Render

    The final value is checked once more and printed as JSON, TOML or YAML, or read into Rust types.

Raise the frame first. Fill it in after.

Declare the shape of your configuration once, then let every file, part and overlay fill it in. Stav keeps the pieces square.

Released under the MIT or Apache 2.0 license, at your option.