diff --git a/lib/jsonschema/default.nix b/lib/jsonschema/default.nix index 177fef8d5..b0bad30de 100644 --- a/lib/jsonschema/default.nix +++ b/lib/jsonschema/default.nix @@ -123,7 +123,7 @@ rec { # parse options to jsonschema properties properties = lib.mapAttrs (_name: option: (parseOption' (path ++ [ _name ]) option)) options'; # TODO: figure out how to handle if prop.anyOf is used - isRequired = prop: !(prop ? default || prop.type or null == "object"); + isRequired = prop: !(prop ? default || prop."$exportedModuleInfo".required or false); requiredProps = lib.filterAttrs (_: prop: isRequired prop) properties; required = lib.optionalAttrs (requiredProps != { }) { required = lib.attrNames requiredProps; }; header' = if addHeader then header else { }; @@ -150,9 +150,9 @@ rec { { }; # Metadata about the module that is made available to the schema via '$propagatedModuleInfo' - exportedModuleInfo = lib.optionalAttrs true (makeModuleInfo { + exportedModuleInfo = makeModuleInfo { inherit path; - }); + }; in # return jsonschema header' @@ -377,7 +377,13 @@ rec { # return jsonschema property definition for attrs then default - // exposedModuleInfo + // (lib.recursiveUpdate exposedModuleInfo ( + lib.optionalAttrs (!default ? default) { + "$exportedModuleInfo" = { + required = true; + }; + } + )) // example // description // { @@ -419,7 +425,19 @@ rec { # return jsonschema property definition for submodule # then (lib.attrNames (option.type.getSubOptions option.loc).opt) then - exposedModuleInfo // example // description // parseSubOptions { inherit option; } + (lib.recursiveUpdate exposedModuleInfo ( + if (default ? default) then + default + else + { + "$exportedModuleInfo" = { + required = true; + }; + } + )) + // example + // description + // parseSubOptions { inherit option; } # throw error if option type is not supported else notSupported option; diff --git a/lib/jsonschema/example-data.json b/lib/jsonschema/example-data.json index 9fb8e0cd7..74b728423 100644 --- a/lib/jsonschema/example-data.json +++ b/lib/jsonschema/example-data.json @@ -16,5 +16,10 @@ "name": "John Doe", "repo": "test-backup" } + }, + "userModules": { + "some-user": { + "foo": {} + } } } diff --git a/lib/jsonschema/example-interface.nix b/lib/jsonschema/example-interface.nix index 9b7c1b9e8..3d1e04d57 100644 --- a/lib/jsonschema/example-interface.nix +++ b/lib/jsonschema/example-interface.nix @@ -20,7 +20,7 @@ default = false; description = "Is the user an admin?"; }; - # a submodule option + # a submodule option without default services = lib.mkOption { type = lib.types.submodule { options.opt = lib.mkOption { @@ -30,6 +30,17 @@ }; }; }; + # a submodule option with default + programs = lib.mkOption { + type = lib.types.submodule { + options.opt = lib.mkOption { + type = lib.types.str; + default = "bar"; + description = "Another submodule option"; + }; + }; + default = { }; + }; # attrs of int userIds = lib.mkOption { type = lib.types.attrsOf lib.types.int; @@ -40,6 +51,14 @@ albrecht = 3; }; }; + # attrs of submodule + userModules = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options.foo = lib.mkOption { }; + } + ); + }; # list of str kernelModules = lib.mkOption { type = lib.types.listOf lib.types.str; diff --git a/lib/jsonschema/example-schema.json b/lib/jsonschema/example-schema.json index 3944cca62..ecb937fc7 100644 --- a/lib/jsonschema/example-schema.json +++ b/lib/jsonschema/example-schema.json @@ -3,6 +3,7 @@ "$exportedModuleInfo": { "path": [] }, "type": "object", "additionalProperties": false, + "required": ["services", "userModules"], "properties": { "name": { "$exportedModuleInfo": { "path": ["name"] }, @@ -46,6 +47,32 @@ }, "description": "Some attributes" }, + "userModules": { + "$exportedModuleInfo": { "path": ["userModules"] }, + "type": "object", + "additionalProperties": { + "$exportedModuleInfo": { "path": ["userModules", ""] }, + "additionalProperties": false, + "type": "object", + "properties": { + "foo": { + "$exportedModuleInfo": { + "path": ["userModules", "", "foo"] + }, + "type": [ + "boolean", + "integer", + "number", + "string", + "array", + "object", + "null" + ] + } + }, + "required": ["foo"] + } + }, "colour": { "$exportedModuleInfo": { "path": ["colour"] }, "default": "red", @@ -65,6 +92,20 @@ } } }, + "programs": { + "$exportedModuleInfo": { "path": ["programs"] }, + "type": "object", + "additionalProperties": false, + "properties": { + "opt": { + "$exportedModuleInfo": { "path": ["programs", "opt"] }, + "type": "string", + "default": "bar", + "description": "Another submodule option" + } + }, + "default": {} + }, "destinations": { "$exportedModuleInfo": { "path": ["destinations"] }, "additionalProperties": { diff --git a/lib/jsonschema/test_parseOption.nix b/lib/jsonschema/test_parseOption.nix index 51f092de1..75ced777c 100644 --- a/lib/jsonschema/test_parseOption.nix +++ b/lib/jsonschema/test_parseOption.nix @@ -324,6 +324,7 @@ in }; }; }; + default = { }; }; }; @@ -354,6 +355,7 @@ in }; }; }; + default = { }; required = [ "opt" ]; }; }; diff --git a/lib/jsonschema/test_parseOptions.nix b/lib/jsonschema/test_parseOptions.nix index 137bf52a1..0c2ff8a93 100644 --- a/lib/jsonschema/test_parseOptions.nix +++ b/lib/jsonschema/test_parseOptions.nix @@ -13,7 +13,17 @@ testParseNestedOptions = let evaled = lib.evalModules { - modules = [ { options.foo.bar = lib.mkOption { type = lib.types.bool; }; } ]; + modules = [ + { + options.foo.bar = lib.mkOption { + type = lib.types.bool; + }; + options.foo.baz = lib.mkOption { + type = lib.types.bool; + default = false; + }; + } + ]; }; in { @@ -40,12 +50,23 @@ }; type = "boolean"; }; + baz = { + "$exportedModuleInfo" = { + path = [ + "foo" + "baz" + ]; + }; + type = "boolean"; + default = false; + }; }; required = [ "bar" ]; type = "object"; }; }; type = "object"; + required = [ "foo" ]; }; };