Skip to content

Errors as data

yen never calls throw. When something is wrong, the read gives null, evaluation continues, and the errors query lists a record. An empty list means the fleet is valid.

A host with a field name that does not exist, bakup for backup, gives an error record:

variants/typo.nix
{ yen, ... }:
{
yen.host.cache = {
addr = "10.0.0.5";
bakup = false;
};
}
result
[
{
class = "fixable";
code = "unknown-field";
error = "unknown field bakup";
fix = "declare yen.kinds.host.fields.bakup, or use a declared field";
from = "host:cache";
near = [
"backup"
];
path = [
"bakup"
];
}
]

Each record has error and from for people, and code, path, got, want, fix, class and near for tools and agents. class is fixable when an edit to the data fixes it, and authority when no edit by this author can. near lists the fields you may have meant. The error reference lists every code.

An aspect key errors gives records, or plain messages. This fleet has three hosts, and each host has an address:

modules/fleet.nix
{ yen, lib, ... }:
{
yen.kinds.fleet = {
spawns.host = _: yen.host;
sinks.out.reduce = yen.lib.reduce.merge;
sinks.warnings.out =
{ warnings, ... }:
{
inherit warnings;
}; # (1) messages that do not fail the fleet
};
yen.kinds.host.fields = {
addr = yen.lib.str;
backup = yen.lib.opt yen.lib.bool true; # (2)
};
yen.fleet.main = { };
yen.host.web.addr = "10.0.0.1";
yen.host.db.addr = "10.0.0.2";
yen.host.scratch = {
addr = "10.0.0.9";
backup = false;
};
yen.flake = { out, ... }: out;
}

At (1), a warnings sink: see Warnings below. At (2), backup is a field of every host, true unless the host says otherwise.

The aspect unique-addr checks that no two hosts share an address. It asks the cycle for a tree of all hosts:

modules/checks.nix
# An aspect key `errors` gives error records, or plain messages. Nothing throws. `from` is always the giving scope.
{ yen, lib, ... }:
let
hosts.instances = yen.kinds.host; # (1) a tree request: every host, with its data
in
{
yen.aspects.unique-addr = {
at = yen.kinds.fleet; # (2)
cycle =
{ tree, ... }:
{
tree = [ hosts ];
errors = [
# (3)
(lib.concatLists (
lib.mapAttrsToList (
a: hs:
lib.optional (lib.length hs > 1) {
code = "duplicate-addr"; # (4)
error = "${a} is the address of ${lib.concatMapStringsSep " and " (h: h.name) hs}";
fix = "give each host its own addr";
path = [ "addr" ];
got = a;
}
) (lib.groupBy (h: h.addr) (tree.select hosts))
))
];
};
};
yen.aspects.no-backup = {
at = yen.lib.all [
yen.kinds.host
(s: !s.backup)
];
warnings = { host, ... }: "${host.name} has no backup"; # (5)
};
}

At (1), a tree request: it asks for every host with its data. At (2), the aspect is active at the fleet only, so the check runs once. At (3), the key errors gives the records. yen sets from to the scope that gave them: an aspect cannot report for another scope. At (4), code names the failure. A record without class is fixable.

A new host with the address of web gives an error record:

variants/duplicate.nix
# A second host with the address of web. The `unique-addr` check reports it, and the fleet gives no outputs.
{
yen.host.cache.addr = "10.0.0.1";
}
result
[
{
class = "fixable";
code = "duplicate-addr";
error = "10.0.0.1 is the address of cache and web";
fix = "give each host its own addr";
from = "fleet:main";
got = "10.0.0.1";
path = [
"addr"
];
}
]

A fleet with an error gives no outputs. Its flake is { invalid-flake-errors = { errors; violations; incomplete; }; } until errors is [ ] and every invariant holds: a build finds no output, and nix eval .#invalid-flake-errors tells you, or an agent, why:

result
{
checks = {
aarch64-darwin = {
yen-lean = <derivation yen-lean-verdict>;
};
aarch64-linux = {
yen-lean = <derivation yen-lean-verdict>;
};
x86_64-linux = {
yen-lean = <derivation yen-lean-verdict>;
};
};
invalid-flake-errors = {
errors = [
{
class = "fixable";
code = "duplicate-addr";
error = "10.0.0.1 is the address of cache and web";
fix = "give each host its own addr";
from = "fleet:main";
got = "10.0.0.1";
path = [
"addr"
];
}
];
incomplete = [ ];
violations = [ ];
};
}

A warning is a message that does not fail the fleet. At (5) in modules/checks.nix, the aspect no-backup gives one for each host without a backup. The warnings sink at (1) in modules/fleet.nix hands them to the flake:

result
[
"scratch has no backup"
]
ContributeCommunitySponsor