diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 8ea8adab72c9d..b3ce45514502c 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -58,12 +58,19 @@ logFailure() { evalConfig() { local attr=$1 shift - local script="import ./default.nix { modules = [ $* ];}" + + local nix_args=() + if [ "${ABORT_ON_WARN-0}" = "1" ]; then - local-nix-instantiate --option abort-on-warn true -E "$script" -A "$attr" - else - local-nix-instantiate -E "$script" -A "$attr" + nix_args+=(--option abort-on-warn true) fi + + if [ "${STRICT_EVAL-0}" = "1" ]; then + nix_args+=(--strict) + fi + + local script="import ./default.nix { modules = [ $* ];}" + local-nix-instantiate "${nix_args[@]}" -E "$script" -A "$attr" } reportFailure() { @@ -247,6 +254,19 @@ checkConfigError 'A definition for option .* is not of type .fileset.. Definitio checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err3 ./fileset.nix checkConfigError 'A definition for option .* is not of type .fileset.. Definition values:\n.*' config.filesetCardinal.err4 ./fileset.nix +# types.mkStructuredType +checkConfigOutput '^null$' config.nullableValue.null ./types.nix +checkConfigOutput '^true$' config.nullableValue.bool ./types.nix +checkConfigOutput '^1$' config.nullableValue.int ./types.nix +checkConfigOutput '^1.1$' config.nullableValue.float ./types.nix +checkConfigOutput '^"foo"$' config.nullableValue.str ./types.nix +checkConfigOutput '^".*/store.*"$' config.nullableValue.path ./types.nix +STRICT_EVAL=1 checkConfigOutput '^\{"foo":1\}$' config.nullableValue.attrs ./types.nix +STRICT_EVAL=1 checkConfigOutput '^\[\{"bar":\[1\]\}\]$' config.nullableValue.list ./types.nix + +checkConfigError 'A definition for option .* is not of type .VAL value.. .*' config.nullableValue.lambda ./types.nix +checkConfigError 'A definition for option .* is not of type .VAL value.. .*' config.structuredValue.null ./types.nix + # Check boolean option. checkConfigOutput '^false$' config.enable ./declare-enable.nix checkConfigError 'The option .* does not exist. Definition values:\n\s*- In .*: true' config.enable ./define-enable.nix diff --git a/lib/tests/modules/types.nix b/lib/tests/modules/types.nix index 0a566ea960c43..78216342fe16b 100644 --- a/lib/tests/modules/types.nix +++ b/lib/tests/modules/types.nix @@ -16,6 +16,17 @@ in options = { pathInStore = mkOption { type = types.lazyAttrsOf types.pathInStore; }; externalPath = mkOption { type = types.lazyAttrsOf types.externalPath; }; + # mkStructuredType + nullableValue = mkOption { type = types.attrsOf (types.mkStructuredType { typeName = "VAL"; }); }; + structuredValue = mkOption { + type = types.attrsOf ( + types.mkStructuredType { + typeName = "VAL"; + nullable = false; + } + ); + }; + assertions = mkOption { }; }; config = { @@ -35,6 +46,22 @@ in externalPath.ok1 = "/foo/bar"; externalPath.ok2 = "/"; + # mkStructuredType { nullable = true; } + nullableValue.null = null; # null + nullableValue.bool = true; # bool + nullableValue.int = 1; # int + nullableValue.float = 1.1; # float + nullableValue.str = "foo"; # str + nullableValue.path = ./.; # path + nullableValue.attrs = { + foo = 1; + }; + nullableValue.list = [ { bar = [ 1 ]; } ]; # list + nullableValue.lambda = x: x; # Error + + # mkStructuredType { nullable = false; } + structuredValue.null = null; # Error + assertions = with lib.types; @@ -486,6 +513,9 @@ in assert (unique { message = "custom"; } (listOf str)).description == "list of string"; assert (unique { message = "test"; } (either int str)).description == "signed integer or string"; assert (unique { message = "test"; } (listOf str)).description == "list of string"; + # json & toml + assert json.description == "JSON value"; + assert toml.description == "TOML value"; # done "ok"; }; diff --git a/lib/types.nix b/lib/types.nix index bc6e28ed93639..711177a681411 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -1436,6 +1436,45 @@ let }; }; + /** + Creates a structured value type suitable for serialization formats. + + Parameters: + - typeName: String describing the format (e.g. "JSON", "YAML", "XML") + - nullable: Whether the structured value type allows `null` values. + + Returns a type suitable for structured data formats that supports: + - Basic types: boolean, integer, float, string, path + - Complex types: attribute sets and lists + */ + mkStructuredType = + { + typeName, + nullable ? true, + }: + let + baseType = oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]; + valueType = (if nullable then nullOr baseType else baseType) // { + description = "${typeName} value"; + }; + in + valueType; + + json = mkStructuredType { typeName = "JSON"; }; + + toml = mkStructuredType { + typeName = "TOML"; + nullable = false; + }; + # Either value of type `t1` or `t2`. either = t1: t2: diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md index 707e2e3d13457..48dba62aabbca 100644 --- a/nixos/doc/manual/development/option-types.section.md +++ b/nixos/doc/manual/development/option-types.section.md @@ -497,6 +497,20 @@ Composed types are types that take a type as parameter. `listOf value of type *`to`*. Can be used to preserve backwards compatibility of an option if its type was changed. +`types.json` + +: A type representing JSON-compatible values. This includes `null`, booleans, + integers, floats, strings, paths, attribute sets, and lists. + Attribute sets and lists can be arbitrarily nested and contain any JSON-compatible + values. + +`types.toml` + +: A type representing TOML-compatible values. This includes booleans, + integers, floats, strings, paths, attribute sets, and lists. + Attribute sets and lists can be arbitrarily nested and contain any TOML-compatible + values. + ## Submodule {#section-option-types-submodule} `submodule` is a very powerful type that defines a set of sub-options diff --git a/pkgs/pkgs-lib/formats.nix b/pkgs/pkgs-lib/formats.nix index 14ca988e9c49a..2897b0201bf20 100644 --- a/pkgs/pkgs-lib/formats.nix +++ b/pkgs/pkgs-lib/formats.nix @@ -40,6 +40,7 @@ let ; inherit (lib.types) + mkStructuredType attrsOf atom bool @@ -58,38 +59,6 @@ let submodule ; - /* - Creates a structured value type suitable for serialization formats. - - Parameters: - - typeName: String describing the format (e.g. "JSON", "YAML", "XML") - - nullable: Whether the structured value type allows `null` values. - - Returns a type suitable for structured data formats that supports: - - Basic types: boolean, integer, float, string, path - - Complex types: attribute sets and lists - */ - mkStructuredType = - { - typeName, - nullable ? true, - }: - let - baseType = oneOf [ - bool - int - float - str - path - (attrsOf valueType) - (listOf valueType) - ]; - valueType = (if nullable then nullOr baseType else baseType) // { - description = "${typeName} value"; - }; - in - valueType; - # Attributes added accidentally in https://github.com/NixOS/nixpkgs/pull/335232 (2024-08-18) # Deprecated in https://github.com/NixOS/nixpkgs/pull/415666 (2025-06) allowAliases = pkgs.config.allowAliases or false; @@ -162,7 +131,7 @@ optionalAttrs allowAliases aliases { }: { - type = mkStructuredType { typeName = "JSON"; }; + type = types.json; generate = name: value: @@ -487,10 +456,7 @@ optionalAttrs allowAliases aliases { }: json { } // { - type = mkStructuredType { - typeName = "TOML"; - nullable = false; - }; + type = types.toml; generate = name: value: