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.

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.

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"
];
}
{
class = "fixable";
code = "duplicate-addr";
error = "10.0.0.1 is the address of db and web";
fix = "give each host its own addr";
from = "fleet:main";
got = "10.0.0.1";
path = [
"addr"
];
}
]

An aspect key errors gives records, or plain messages. yen sets from to the scope that gave it: an aspect cannot report for another scope. A record without class is fixable. This fleet has two hosts with one 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;
}; # messages that do not fail the fleet
};
yen.kinds.host.fields = {
addr = lib.types.str;
backup = yen.lib.opt lib.types.bool true;
};
yen.fleet.main = { };
yen.host.web.addr = "10.0.0.1";
yen.host.db.addr = "10.0.0.1";
yen.host.scratch = {
addr = "10.0.0.9";
backup = false;
};
yen.flake = { out, ... }: out;
}
modules/checks.nix
# An aspect key `errors` gives error records, or plain messages. Nothing throws. `from` is always the giving scope.
{ yen, lib, ... }:
let
byAddr = lib.groupBy (h: h.addr) (lib.attrValues yen.host);
in
{
yen.aspects.unique-addr = {
at = yen.kinds.fleet;
errors = lib.concatLists (
lib.mapAttrsToList (
a: hs:
lib.optional (lib.length hs > 1) {
code = "duplicate-addr";
error = "${a} is the address of ${lib.concatMapStringsSep " and " (h: h.name) hs}";
fix = "give each host its own addr";
path = [ "addr" ];
got = a;
}
) byAddr
);
};
yen.aspects.no-backup = {
at = yen.lib.all [
yen.kinds.host
(s: !s.backup)
];
warnings = { host, ... }: "${host.name} has no backup";
};
}
result
[
{
class = "fixable";
code = "duplicate-addr";
error = "10.0.0.1 is the address of db and web";
fix = "give each host its own addr";
from = "fleet:main";
got = "10.0.0.1";
path = [
"addr"
];
}
]

A warning is a message that does not fail the fleet. It is a plain sink with list reduce, here given to the flake as warnings:

result
[
"scratch has no backup"
]
Contribute Community Sponsor