Merge pull request #284512 from hercules-ci/lib-types-unique-merge
lib.types.unique: Check inner type deeply
This commit is contained in:
commit
f37ba19765
5 changed files with 63 additions and 19 deletions
|
@ -254,13 +254,31 @@ rec {
|
|||
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}";
|
||||
|
||||
/*
|
||||
Require a single definition.
|
||||
|
||||
WARNING: Does not perform nested checks, as this does not run the merge function!
|
||||
*/
|
||||
mergeOneOption = mergeUniqueOption { message = ""; };
|
||||
|
||||
mergeUniqueOption = { message }: loc: defs:
|
||||
if length defs == 1
|
||||
then (head defs).value
|
||||
else 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}";
|
||||
/*
|
||||
Require a single definition.
|
||||
|
||||
NOTE: When the type is not checked completely by check, pass a merge function for further checking (of sub-attributes, etc).
|
||||
*/
|
||||
mergeUniqueOption = args@{
|
||||
message,
|
||||
# WARNING: the default merge function assumes that the definition is a valid (option) value. You MUST pass a merge function if the return value needs to be
|
||||
# - type checked beyond what .check does (which should be very litte; only on the value head; not attribute values, etc)
|
||||
# - if you want attribute values to be checked, or list items
|
||||
# - if you want coercedTo-like behavior to work
|
||||
merge ? loc: defs: (head defs).value }:
|
||||
loc: defs:
|
||||
if length defs == 1
|
||||
then merge loc defs
|
||||
else
|
||||
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}";
|
||||
|
||||
/* "Merge" option definitions by checking that they all have the same value. */
|
||||
mergeEqualOption = loc: defs:
|
||||
|
|
|
@ -407,6 +407,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 .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
|
||||
checkConfigOutput '^true$' config.unprocessedNestingEvaluates.success ./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;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -614,23 +614,12 @@ rec {
|
|||
nestedTypes.elemType = elemType;
|
||||
};
|
||||
|
||||
# Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
|
||||
uniq = elemType: mkOptionType rec {
|
||||
name = "uniq";
|
||||
inherit (elemType) description descriptionClass check;
|
||||
merge = mergeOneOption;
|
||||
emptyValue = elemType.emptyValue;
|
||||
getSubOptions = elemType.getSubOptions;
|
||||
getSubModules = elemType.getSubModules;
|
||||
substSubModules = m: uniq (elemType.substSubModules m);
|
||||
functor = (defaultFunctor name) // { wrapped = elemType; };
|
||||
nestedTypes.elemType = elemType;
|
||||
};
|
||||
uniq = unique { message = ""; };
|
||||
|
||||
unique = { message }: type: mkOptionType rec {
|
||||
name = "unique";
|
||||
inherit (type) description descriptionClass check;
|
||||
merge = mergeUniqueOption { inherit message; };
|
||||
merge = mergeUniqueOption { inherit message; inherit (type) merge; };
|
||||
emptyValue = type.emptyValue;
|
||||
getSubOptions = type.getSubOptions;
|
||||
getSubModules = type.getSubModules;
|
||||
|
|
|
@ -326,7 +326,7 @@ Composed types are types that take a type as parameter. `listOf
|
|||
`types.uniq` *`t`*
|
||||
|
||||
: Ensures that type *`t`* cannot be merged. It is used to ensure option
|
||||
definitions are declared only once.
|
||||
definitions are provided only once.
|
||||
|
||||
`types.unique` `{ message = m }` *`t`*
|
||||
|
||||
|
|
Loading…
Reference in a new issue