# Assertions about names an operator chooses, so a module states WHAT is # forbidden and not how to phrase the refusal. { lib }: let # One shape for both guards. Reports EVERY offender, not the first: fixing # one per rebuild is the slowest possible way to learn a rule. # # Offenders come after their label rather than before it, so one name and # five read the same — no English verb agrees with both. guard = { option, names, forbidden, hit, problem, forbiddenLabel, why, }: let offenders = lib.filter (n: lib.any (f: hit f n) forbidden) names; quote = xs: lib.concatMapStringsSep ", " (x: "'${x}'") xs; in { assertion = offenders == [ ]; message = '' ${option} ${problem}: ${quote offenders} ${forbiddenLabel}: ${quote forbidden} ${why} ''; }; in { # No name may BE one of `reserved`. mustNotEqual = { option, names, reserved, why, }: guard { inherit option names why; forbidden = reserved; hit = f: n: f == n; problem = "has reserved name(s)"; forbiddenLabel = "Reserved"; }; # No name may CONTAIN one of `fragments`. mustNotContain = { option, names, fragments, why, }: guard { inherit option names why; forbidden = fragments; hit = lib.hasInfix; problem = "has name(s) containing a reserved word"; forbiddenLabel = "Forbidden as substrings"; }; # Every name must be a valid hive `Ident` (hive-types/src/lib.rs): 1-63 # characters of `[a-z0-9-]`, with no leading-character rule. A hive name is # spliced into certificate subjects and filesystem paths, where `/` and `..` # change meaning. mustBeIdent = { option, names }: let offenders = lib.filter (n: builtins.match "[a-z0-9-]+" n == null || lib.stringLength n > 63) names; in { assertion = offenders == [ ]; message = '' ${option} has name(s) that are not valid identifiers: ${ lib.concatMapStringsSep ", " (x: "'${x}'") offenders } Required: 1-63 characters, each one of [a-z0-9-] A hive name becomes a certificate subject and a directory name, so a `/` or `..` in it names something else. `Ident` in hive-types/src/lib.rs applies the same rule. Rename the hive. ''; }; }