Merge pull request 'lib/jsonschema: make attrs required' (#3335) from fricklerhandwerk/clan-core:required-attrs into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3335
This commit is contained in:
Mic92
2025-04-16 18:26:46 +00:00
6 changed files with 113 additions and 7 deletions

View File

@@ -123,7 +123,7 @@ rec {
# parse options to jsonschema properties # parse options to jsonschema properties
properties = lib.mapAttrs (_name: option: (parseOption' (path ++ [ _name ]) option)) options'; properties = lib.mapAttrs (_name: option: (parseOption' (path ++ [ _name ]) option)) options';
# TODO: figure out how to handle if prop.anyOf is used # 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; requiredProps = lib.filterAttrs (_: prop: isRequired prop) properties;
required = lib.optionalAttrs (requiredProps != { }) { required = lib.attrNames requiredProps; }; required = lib.optionalAttrs (requiredProps != { }) { required = lib.attrNames requiredProps; };
header' = if addHeader then header else { }; header' = if addHeader then header else { };
@@ -150,9 +150,9 @@ rec {
{ }; { };
# Metadata about the module that is made available to the schema via '$propagatedModuleInfo' # Metadata about the module that is made available to the schema via '$propagatedModuleInfo'
exportedModuleInfo = lib.optionalAttrs true (makeModuleInfo { exportedModuleInfo = makeModuleInfo {
inherit path; inherit path;
}); };
in in
# return jsonschema # return jsonschema
header' header'
@@ -377,7 +377,13 @@ rec {
# return jsonschema property definition for attrs # return jsonschema property definition for attrs
then then
default default
// exposedModuleInfo // (lib.recursiveUpdate exposedModuleInfo (
lib.optionalAttrs (!default ? default) {
"$exportedModuleInfo" = {
required = true;
};
}
))
// example // example
// description // description
// { // {
@@ -419,7 +425,19 @@ rec {
# return jsonschema property definition for submodule # return jsonschema property definition for submodule
# then (lib.attrNames (option.type.getSubOptions option.loc).opt) # then (lib.attrNames (option.type.getSubOptions option.loc).opt)
then 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 # throw error if option type is not supported
else else
notSupported option; notSupported option;

View File

@@ -16,5 +16,10 @@
"name": "John Doe", "name": "John Doe",
"repo": "test-backup" "repo": "test-backup"
} }
},
"userModules": {
"some-user": {
"foo": {}
}
} }
} }

View File

@@ -20,7 +20,7 @@
default = false; default = false;
description = "Is the user an admin?"; description = "Is the user an admin?";
}; };
# a submodule option # a submodule option without default
services = lib.mkOption { services = lib.mkOption {
type = lib.types.submodule { type = lib.types.submodule {
options.opt = lib.mkOption { 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 # attrs of int
userIds = lib.mkOption { userIds = lib.mkOption {
type = lib.types.attrsOf lib.types.int; type = lib.types.attrsOf lib.types.int;
@@ -40,6 +51,14 @@
albrecht = 3; albrecht = 3;
}; };
}; };
# attrs of submodule
userModules = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
options.foo = lib.mkOption { };
}
);
};
# list of str # list of str
kernelModules = lib.mkOption { kernelModules = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;

View File

@@ -3,6 +3,7 @@
"$exportedModuleInfo": { "path": [] }, "$exportedModuleInfo": { "path": [] },
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["services", "userModules"],
"properties": { "properties": {
"name": { "name": {
"$exportedModuleInfo": { "path": ["name"] }, "$exportedModuleInfo": { "path": ["name"] },
@@ -46,6 +47,32 @@
}, },
"description": "Some attributes" "description": "Some attributes"
}, },
"userModules": {
"$exportedModuleInfo": { "path": ["userModules"] },
"type": "object",
"additionalProperties": {
"$exportedModuleInfo": { "path": ["userModules", "<name>"] },
"additionalProperties": false,
"type": "object",
"properties": {
"foo": {
"$exportedModuleInfo": {
"path": ["userModules", "<name>", "foo"]
},
"type": [
"boolean",
"integer",
"number",
"string",
"array",
"object",
"null"
]
}
},
"required": ["foo"]
}
},
"colour": { "colour": {
"$exportedModuleInfo": { "path": ["colour"] }, "$exportedModuleInfo": { "path": ["colour"] },
"default": "red", "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": { "destinations": {
"$exportedModuleInfo": { "path": ["destinations"] }, "$exportedModuleInfo": { "path": ["destinations"] },
"additionalProperties": { "additionalProperties": {

View File

@@ -324,6 +324,7 @@ in
}; };
}; };
}; };
default = { };
}; };
}; };
@@ -354,6 +355,7 @@ in
}; };
}; };
}; };
default = { };
required = [ "opt" ]; required = [ "opt" ];
}; };
}; };

View File

@@ -13,7 +13,17 @@
testParseNestedOptions = testParseNestedOptions =
let let
evaled = lib.evalModules { 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 in
{ {
@@ -40,12 +50,23 @@
}; };
type = "boolean"; type = "boolean";
}; };
baz = {
"$exportedModuleInfo" = {
path = [
"foo"
"baz"
];
};
type = "boolean";
default = false;
};
}; };
required = [ "bar" ]; required = [ "bar" ];
type = "object"; type = "object";
}; };
}; };
type = "object"; type = "object";
required = [ "foo" ];
}; };
}; };