lib.types.unique: Check inner type deeply
This doesn't change uniq. Why not? - In NixOS it seems that uniq is only used with simple types that are fully checked by t.check. - It exists for much longer and is used more widely. - I believe we should deprecate it, because unique was already better. - unique can be a proving ground.
This commit is contained in:
parent
0e756e65d5
commit
b78ba9bc68
4 changed files with 66 additions and 6 deletions
|
@ -254,13 +254,36 @@ rec {
|
||||||
else if all isInt list && all (x: x == head list) list then head list
|
else if all isInt list && all (x: x == head list) list then head list
|
||||||
else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
|
else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}";
|
||||||
|
|
||||||
|
/*
|
||||||
|
Require a single definition.
|
||||||
|
|
||||||
|
WARNING: Does not perform nested checks, as this does not run the merge function!
|
||||||
|
*/
|
||||||
mergeOneOption = mergeUniqueOption { message = ""; };
|
mergeOneOption = mergeUniqueOption { message = ""; };
|
||||||
|
|
||||||
mergeUniqueOption = { message }: loc: defs:
|
/*
|
||||||
if length defs == 1
|
Require a single definition.
|
||||||
then (head defs).value
|
|
||||||
else assert length defs > 1;
|
NOTE: When the type is not checked completely by check, pass a merge function for further checking (of sub-attributes, etc).
|
||||||
throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}";
|
*/
|
||||||
|
mergeUniqueOption = args@{ message, merge ? null }:
|
||||||
|
let
|
||||||
|
notUnique = loc: defs:
|
||||||
|
assert length defs > 1;
|
||||||
|
throw "The option `${showOption loc}' is defined multiple times while it's expected to be unique.\n${message}\nDefinition values:${showDefs defs}\n${prioritySuggestion}";
|
||||||
|
in
|
||||||
|
if merge == null
|
||||||
|
# The inner conditional could be factored out, but this way we take advantage of partial application.
|
||||||
|
then
|
||||||
|
loc: defs:
|
||||||
|
if length defs == 1
|
||||||
|
then (head defs).value
|
||||||
|
else notUnique loc defs
|
||||||
|
else
|
||||||
|
loc: defs:
|
||||||
|
if length defs == 1
|
||||||
|
then merge loc defs
|
||||||
|
else notUnique loc defs;
|
||||||
|
|
||||||
/* "Merge" option definitions by checking that they all have the same value. */
|
/* "Merge" option definitions by checking that they all have the same value. */
|
||||||
mergeEqualOption = loc: defs:
|
mergeEqualOption = loc: defs:
|
||||||
|
|
|
@ -406,6 +406,16 @@ checkConfigOutput "{}" config.submodule.a ./emptyValues.nix
|
||||||
checkConfigError 'The option .int.a. is used but not defined' config.int.a ./emptyValues.nix
|
checkConfigError 'The option .int.a. is used but not defined' config.int.a ./emptyValues.nix
|
||||||
checkConfigError 'The option .nonEmptyList.a. is used but not defined' config.nonEmptyList.a ./emptyValues.nix
|
checkConfigError 'The option .nonEmptyList.a. is used but not defined' config.nonEmptyList.a ./emptyValues.nix
|
||||||
|
|
||||||
|
# types.unique
|
||||||
|
# requires a single definition
|
||||||
|
checkConfigError 'The option .examples\.merged. is defined multiple times while it.s expected to be unique' config.examples.merged.a ./types-unique.nix
|
||||||
|
# user message is printed
|
||||||
|
checkConfigError 'We require a single definition, because seeing the whole value at once helps us maintain critical invariants of our system.' config.examples.merged.a ./types-unique.nix
|
||||||
|
# let the inner merge function check the values (on demand)
|
||||||
|
checkConfigError 'A definition for option .examples\.badLazyType\.a. is not of type .string.' config.examples.badLazyType.a ./types-unique.nix
|
||||||
|
# overriding still works (unlike option uniqueness)
|
||||||
|
checkConfigOutput '^"bee"$' config.examples.override.b ./types-unique.nix
|
||||||
|
|
||||||
## types.raw
|
## types.raw
|
||||||
checkConfigOutput '^true$' config.unprocessedNestingEvaluates.success ./raw.nix
|
checkConfigOutput '^true$' config.unprocessedNestingEvaluates.success ./raw.nix
|
||||||
checkConfigOutput "10" config.processedToplevel ./raw.nix
|
checkConfigOutput "10" config.processedToplevel ./raw.nix
|
||||||
|
|
27
lib/tests/modules/types-unique.nix
Normal file
27
lib/tests/modules/types-unique.nix
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.examples = mkOption {
|
||||||
|
type = types.lazyAttrsOf
|
||||||
|
(types.unique
|
||||||
|
{ message = "We require a single definition, because seeing the whole value at once helps us maintain critical invariants of our system."; }
|
||||||
|
(types.attrsOf types.str));
|
||||||
|
};
|
||||||
|
imports = [
|
||||||
|
{ examples.merged = { b = "bee"; }; }
|
||||||
|
{ examples.override = lib.mkForce { b = "bee"; }; }
|
||||||
|
];
|
||||||
|
config.examples = {
|
||||||
|
merged = {
|
||||||
|
a = "aye";
|
||||||
|
};
|
||||||
|
override = {
|
||||||
|
a = "aye";
|
||||||
|
};
|
||||||
|
badLazyType = {
|
||||||
|
a = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -629,7 +629,7 @@ rec {
|
||||||
unique = { message }: type: mkOptionType rec {
|
unique = { message }: type: mkOptionType rec {
|
||||||
name = "unique";
|
name = "unique";
|
||||||
inherit (type) description descriptionClass check;
|
inherit (type) description descriptionClass check;
|
||||||
merge = mergeUniqueOption { inherit message; };
|
merge = mergeUniqueOption { inherit message; inherit (type) merge; };
|
||||||
emptyValue = type.emptyValue;
|
emptyValue = type.emptyValue;
|
||||||
getSubOptions = type.getSubOptions;
|
getSubOptions = type.getSubOptions;
|
||||||
getSubModules = type.getSubModules;
|
getSubModules = type.getSubModules;
|
||||||
|
|
Loading…
Reference in a new issue