about summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorRobert Hensing <robert@roberthensing.nl>2024-01-28 00:30:36 +0100
committerRobert Hensing <robert@roberthensing.nl>2024-05-18 17:50:01 +0200
commit01fabba9fe8d3ce77cbeb380cb28c19038ec0b84 (patch)
tree1312a2d3c95fc964e1e0829c547e2f928e89f2a4 /lib
parentbacb8503d3a51d9e9b52e52a1ba45e2c380ad07d (diff)
lib.types.attrTag: init
Diffstat (limited to 'lib')
-rwxr-xr-xlib/tests/modules.sh11
-rw-r--r--lib/tests/modules/types-attrTag.nix64
-rw-r--r--lib/types.nix37
3 files changed, 112 insertions, 0 deletions
diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh
index cfd36bfff49bf..609a002e6dc32 100755
--- a/lib/tests/modules.sh
+++ b/lib/tests/modules.sh
@@ -94,6 +94,17 @@ checkConfigOutput '^true$' config.result ./module-argument-default.nix
 # gvariant
 checkConfigOutput '^true$' config.assertion ./gvariant.nix
 
+# types.attrTag
+checkConfigOutput '^true$' config.okChecks ./types-attrTag.nix
+checkConfigError 'A definition for option .intStrings\.syntaxError. is not of type .attribute-tagged union of left, right' config.intStrings.syntaxError ./types-attrTag.nix
+checkConfigError 'A definition for option .intStrings\.syntaxError2. is not of type .attribute-tagged union of left, right' config.intStrings.syntaxError2 ./types-attrTag.nix
+checkConfigError 'A definition for option .intStrings\.syntaxError3. is not of type .attribute-tagged union of left, right' config.intStrings.syntaxError3 ./types-attrTag.nix
+checkConfigError 'A definition for option .intStrings\.syntaxError4. is not of type .attribute-tagged union of left, right' config.intStrings.syntaxError4 ./types-attrTag.nix
+checkConfigError 'A definition for option .intStrings\.mergeError. is not of type .attribute-tagged union of left, right' config.intStrings.mergeError ./types-attrTag.nix
+checkConfigError 'A definition for option .intStrings\.badTagError. is not of type .attribute-tagged union of left, right' config.intStrings.badTagError ./types-attrTag.nix
+checkConfigError 'A definition for option .intStrings\.badTagTypeError\.left. is not of type .signed integer.' config.intStrings.badTagTypeError.left ./types-attrTag.nix
+checkConfigError 'A definition for option .nested\.right\.left. is not of type .signed integer.' config.nested.right.left ./types-attrTag.nix
+
 # types.pathInStore
 checkConfigOutput '".*/store/0lz9p8xhf89kb1c1kk6jxrzskaiygnlh-bash-5.2-p15.drv"' config.pathInStore.ok1 ./types.nix
 checkConfigOutput '".*/store/0fb3ykw9r5hpayd05sr0cizwadzq1d8q-bash-5.2-p15"' config.pathInStore.ok2 ./types.nix
diff --git a/lib/tests/modules/types-attrTag.nix b/lib/tests/modules/types-attrTag.nix
new file mode 100644
index 0000000000000..08854ca73f566
--- /dev/null
+++ b/lib/tests/modules/types-attrTag.nix
@@ -0,0 +1,64 @@
+{ lib, config, ... }:
+let
+  inherit (lib) mkOption types;
+  forceDeep = x: builtins.deepSeq x x;
+in
+{
+  options = {
+    intStrings = mkOption {
+      type = types.attrsOf
+        (types.attrTag {
+          left = types.int;
+          right = types.str;
+        });
+    };
+    nested = mkOption {
+      type = types.attrTag {
+        left = types.int;
+        right = types.attrTag {
+          left = types.int;
+          right = types.str;
+        };
+      };
+    };
+    merged = mkOption {
+      type = types.attrsOf (
+        types.attrTag {
+          yay = types.int;
+        }
+      );
+    };
+    okChecks = mkOption {};
+  };
+  imports = [
+    {
+      options.merged = mkOption {
+        type = types.attrsOf (
+          types.attrTag {
+            nay = types.bool;
+          }
+        );
+      };
+    }
+  ];
+  config = {
+    intStrings.syntaxError = 1;
+    intStrings.syntaxError2 = {};
+    intStrings.syntaxError3 = { a = true; b = true; };
+    intStrings.syntaxError4 = lib.mkMerge [ { a = true; } { b = true; } ];
+    intStrings.mergeError = lib.mkMerge [ { int = throw "do not eval"; } { string = throw "do not eval"; } ];
+    intStrings.badTagError.rite = throw "do not eval";
+    intStrings.badTagTypeError.left = "bad";
+    intStrings.numberOne.left = 1;
+    intStrings.hello.right = "hello world";
+    nested.right.left = "not a number";
+    merged.negative.nay = false;
+    merged.positive.yay = 100;
+    okChecks =
+      assert config.intStrings.hello.right == "hello world";
+      assert config.intStrings.numberOne.left == 1;
+      assert config.merged.negative.nay == false;
+      assert config.merged.positive.yay == 100;
+      true;
+  };
+}
diff --git a/lib/types.nix b/lib/types.nix
index 51e58eaa8ab51..26b30fde8c417 100644
--- a/lib/types.nix
+++ b/lib/types.nix
@@ -602,6 +602,43 @@ rec {
       nestedTypes.elemType = elemType;
     };
 
+    attrTag = tags: attrTagWith { inherit tags; };
+
+    attrTagWith = { tags }:
+      let
+        choicesStr = concatMapStringsSep ", " lib.strings.escapeNixIdentifier (attrNames tags);
+      in
+      mkOptionType {
+        name = "attrTag";
+        description = "attribute-tagged union of ${choicesStr}";
+        getSubModules = null;
+        substSubModules = m: attrTagWith { tags = mapAttrs (n: v: v.substSubModules m) tags; };
+        check = v: isAttrs v && length (attrNames v) == 1 && tags?${head (attrNames v)};
+        merge = loc: defs:
+          let
+            choice = head (attrNames (head defs).value);
+            checkedValueDefs = map
+              (def:
+                assert (length (attrNames def.value)) == 1;
+                if (head (attrNames def.value)) != choice
+                then throw "The option `${showOption loc}` is defined both as `${choice}` and `${head (attrNames def.value)}`, in ${showFiles (getFiles defs)}."
+                else { inherit (def) file; value = def.value.${choice}; })
+              defs;
+          in
+            if tags?${choice}
+            then
+              { ${choice} =
+                  (mergeDefinitions
+                    (loc ++ [choice])
+                    tags.${choice}
+                    checkedValueDefs
+                  ).mergedValue;
+              }
+            else throw "The option `${showOption loc}` is defined as ${lib.strings.escapeNixIdentifier choice}, but ${lib.strings.escapeNixIdentifier choice} is not among the valid choices (${choicesStr}). Value ${choice} was defined in ${showFiles (getFiles defs)}.";
+        nestedTypes = tags;
+        functor = (defaultFunctor "attrTagWith") // { payload = { inherit tags; }; binOp = a: b: { tags = a.tags // b.tags; }; };
+      };
+
     # Value of given type but with no merging (i.e. `uniq list`s are not concatenated).
     uniq = elemType: mkOptionType rec {
       name = "uniq";