values
bind
pub fn bind(Node, @moonmodel.Model, known? : Array[@moonmodel.Model], knobs? : Bind) -> @json.Json raise RefusedCheck a tree against a model and hand back the bound values as JSON, with defaults filled in and text read as the type it spells.
The JSON is the shape the model describes, so @json.from_json turns it into a struct of the caller's own. known resolves Ref fields by model name; a reference nothing resolves is carried through unchecked rather than guessed at.
closed
pub let closed : BindBind the members the model describes and refuse the rest.
decimal
pub fn decimal(StringView) -> Double?The real number text spells, or nothing.
layered
pub let layered : KnobsDefaults below files below the environment below explicit overrides, with the stronger layer winning silently.
merge
pub fn merge(Array[Source], knobs? : Knobs) -> Node raise RefusedMerge a whole stack, weakest first, in the order knobs asks for.
Sources of the same tier keep the order they were given, so Source order is the tie-break and a caller never has to number anything.
native
pub fn native(Node) -> @json.JsonThe tree as JSON, keeping the shape and dropping the provenance.
of_json
pub fn of_json(StringView, source? : String) -> Node raise RefusedRead a JSON document into the tree.
Positions are not carried: the reader this borrows reports where it refused, not where each value sat, so a value carries the source name and the first position. A refusal keeps the reader's own position.
of_toml
pub fn of_toml(StringView, source? : String) -> Node raise RefusedRead a TOML document into the tree.
of_yaml
pub fn of_yaml(StringView, source? : String) -> Node raise RefusedRead a YAML document into the tree.
One document, in YAML 1.2 core schema. Anchors, aliases and merge keys are resolved by the reader; what arrives here is the value.
open
pub let open : BindBind the members the model describes and carry the rest through.
segments
pub fn segments(String) -> Array[String] raise RefusedSplit a dotted path into its segments.
A dot separates members; \. is a literal dot, so a key that contains one can still be named. An empty segment — a..b, .a, a. — is refused rather than skipped, because a path with a hole in it is a typo.
spot
pub fn spot(String, At) -> StringThe source and position part of a refusal, rendered as name@line:column.
start
pub let start : AtThe first position in a source: what to use when nothing better is known.
validate
pub fn validate(Node, @moonmodel.Model, known? : Array[@moonmodel.Model], knobs? : Bind) -> Array[Refused]Every way node fails to be what model describes, in tree order.
Binding stops at the first refusal, because a caller who is going to fail wants one clear message. A caller who wants to show a person everything wrong with a file at once uses this: it walks the whole tree and collects.
whole
pub fn whole(StringView) -> Int64?The whole number text spells, or nothing.
Configuration text is mostly digits that a program has to read as a number, and the readers in this repository refuse rather than guess. Nothing is trimmed here: a value with a space in it is not a number, and saying so is better than quietly accepting it.
types
At
pub(all) struct AtWhere in a document a value was written.
The offset is in UTF-16 code units from the start of the source, which is what a StringView indexes by. Line and column are one-based, because that is how an editor counts, and an error a reader cannot find in their file is half an error.
A source that does not report positions — the JSON, YAML and TOML readers this package borrows keep the tree but not the offsets — fills in { at: 0, line: 1, column: 1 }, and says so where it matters.
Fields
at : Int32The offset in UTF-16 code units from the start of the source.
line : Int32The line the offset is on, counting from one.
column : Int32The column the offset is at, counting from one.
Methods
pub fn At::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn At::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn At::of(StringView, Int32) -> SelfThe position of offset in text, counted only when something has already gone wrong.
pub fn At::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
Bind
pub(all) struct BindHow to bind.
Knobs
pub(all) struct KnobsHow to merge: the order of the tiers, and what two layers that disagree mean.
order runs from the weakest tier to the strongest, and a tier left out of it sorts below every tier in it, in the order the sources were given. Putting the tiers in a different order is how a caller reverses the whole stack, an overlay read after the base file say, without touching any parser.
Fields
order : Array[Tier]Tiers from weakest to strongest: the last one wins.
clash : @moonknob.OnClash[Node]What a value set differently by two layers means to the program: taken from the stronger layer silently or with a warning, aborted on, or decided by the caller's function. moonknob's, as in every library of the family.
strict : BoolWhether such a value is an error in the configuration itself, raised as Clash with both positions, for a deployment where an accidental override is a bug. Checked before clash.
Node
pub(all) struct NodeA value and the provenance that makes an error message usable: which source it came from, and where in that source it was written.
Fields
value : ValueThe value itself.
source : StringThe name the caller gave the source — a file name, env, defaults.
at : AtWhere in that source the value was written.
Methods
pub fn Node::bool(Self) -> Bool raise RefusedThe boolean.
pub fn Node::copy(Self) -> SelfA deep copy: the tree a caller gets back is theirs to change without disturbing the one it came from.
pub fn Node::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Node::find(Self, String) -> Self?The value at path, or nothing.
A segment of digits indexes into a list, so servers.0.host names the first server's host without a second notation to learn.
pub fn Node::flag(Bool, String, at? : At) -> SelfA boolean node.
pub fn Node::from(Self, String, at? : At) -> SelfThe same value, said to come from somewhere else. A merge uses this to remember which layer won.
pub fn Node::has(Self, String) -> BoolWhether a path is present.
pub fn Node::int(Self) -> Int64 raise RefusedThe integer, or a Type refusal. A real with no fractional part is accepted, because 8080.0 and 8080 are the same port.
pub fn Node::is_list(Self) -> BoolWhether this is a list.
pub fn Node::is_map(Self) -> BoolWhether this is a mapping.
pub fn Node::kind(Self) -> StringThe name of the shape a value has, for error messages: text, integer, real, boolean, null, list, mapping.
pub fn Node::list(Self) -> Array[Self] raise RefusedThe members of a list.
pub fn Node::map(Array[(String, Self)], String, at? : At) -> SelfA mapping node built from pairs, in the order given.
pub fn Node::merge(Self, Self, clash? : @moonknob.OnClash[Self], strict? : Bool, path? : String) -> Self raise RefusedMerge two trees. over is the stronger one; strict and clash decide what happens where they disagree, as in Knobs.
Mappings merge member by member, so a later file that sets one key under db keeps the rest of the earlier file's db. Lists replace rather than concatenate: a list in a configuration is nearly always one decision, which hosts or which plugins, and appending two decisions gives a value nobody wrote.
pub fn Node::need(Self, String) -> Self raise RefusedThe value at path, or a Missing refusal naming the path.
pub fn Node::need_bool(Self, String) -> Bool raise RefusedThe boolean at path.
pub fn Node::need_int(Self, String) -> Int64 raise RefusedThe integer at path.
pub fn Node::need_text(Self, String) -> String raise RefusedThe text at path.
A value of another shape is refused with the path in the sentence, because a caller who asked for db.port wants to be told about db.port rather than about a position in a file.
pub fn Node::new(Value, String, At) -> SelfA node of any value, with the source it came from and where in it.
pub fn Node::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Node::num(Int64, String, at? : At) -> SelfAn integer node.
pub fn Node::of_real(Double, String, at? : At) -> SelfA real-number node.
pub fn Node::real(Self) -> Double raise RefusedThe number as a real.
pub fn Node::seq(Array[Self], String, at? : At) -> SelfA list node.
pub fn Node::str(String, String, at? : At) -> SelfA text node.
pub fn Node::table(Self) -> Table raise RefusedThe members of a mapping.
pub fn Node::text(Self) -> String raise RefusedThe text, or a Type refusal naming what was there instead.
pub fn Node::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
Source
pub(all) struct SourceOne layer of a configuration: a tree, the tier it belongs to, and the name that goes into error messages.
Table
pub(all) struct TableA mapping that remembers the order its members arrived in, because a configuration file does: an INI file written back with its sections sorted alphabetically is a different file, and a person diffing it sees noise instead of their change.
Methods
pub fn Table::copy(Self) -> SelfA copy, one level deep: the members are shared, the mapping is not.
pub fn Table::equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Table::get(Self, String) -> Node?The member called key, or nothing.
pub fn Table::has(Self, String) -> BoolWhether key has a member.
pub fn Table::is_empty(Self) -> BoolWhether the mapping has no members.
pub fn Table::items(Self) -> ArrayView[(String, Node)]The members in the order they were written.
pub fn Table::keys(Self) -> Array[String]The names, in the order they were written.
pub fn Table::length(Self) -> Int32How many members the mapping has.
pub fn Table::new() -> SelfAn empty mapping.
pub fn Table::not_equal(Self, Self) -> BoolWritten by derive(Eq).
pub fn Table::remove(Self, String) -> Node?Drop key, giving back what it held.
pub fn Table::set(Self, String, Node) -> UnitPut value under key, in the place key already holds if it holds one.
Keeping the position is what makes a merge readable: the winner of a clash stays where the losing value was, so the merged document still reads in the order a person wrote it.
pub fn Table::to_repr(Self) -> @debug.ReprWritten by derive(Debug).
Tier
pub(all) enum TierWhich layer of a configuration a source belongs to.
The tiers are here rather than as a number the caller assigns because the order almost everybody wants — defaults, then files, then the environment, then whatever the command line said — is a fact about deployment, not about this repository, and naming it means a caller who wants the usual thing does not write it out.
Variants
DefaultValues the program carries with it, used when nothing overrides them.
FileA file the deployment shipped, or the one named on the command line.
EnvThe process environment.
OverrideWhatever the caller declares last: flags, a test fixture, an override.
Value
pub(all) enum ValueA value in a configuration tree.
Numbers keep their kind: an integer stays an integer so that a port number written as 8080 is not printed back as 8080.0, and so that a binding to an Int field can accept it without guessing.
Variants
NullNothing was written, and nothing was meant.
Bool(Bool)true or false.
Int(Int64)A whole number.
Real(Double)A number with a fractional part.
Text(String)Text, however it was quoted.
List(Array[Node])An ordered sequence.
Map(Table)A mapping, in the order its members were written.
errors
Refused
pub(all) suberror RefusedWhy a configuration was refused, and where.
One error type for the whole repository's root package: reading a borrowed format, merging layers, looking a path up, binding to a model. A caller catches one thing and reads one message. The message names the source and the position when the value came from text, and names the field path — db.pool.size — whenever the refusal is about a place in the tree, because a message that says "expected an integer" without saying where is a message that sends the reader looking through the whole file.
Variants
Type(want~ : String, got~ : String, path~ : String, source~ : String, at~ : At)A value has the wrong shape. path is empty when the refusal is about a value asked for on its own, with no place in the tree to name.
Missing(path~ : String, want~ : String)A field the model requires was nowhere in the sources.
Unknown(path~ : String, got~ : String)A member no field of the model describes, under a closed binding.
Clash(path~ : String, first~ : String, second~ : String)Two layers set one value differently, under a strict merge.
Path(got~ : String, at~ : Int32)A path that does not parse: an empty segment, a stray dot.
Format(why~ : String, at~ : At?)A borrowed reader refused the document, or a value it produced could not be carried into the tree. at is the position the reader reported, when it reported one.
TooDeep(at~ : At, limit~ : Int32)Nesting past the limit. A document of ten thousand opens must not take the stack down with it.