From 34873828ed077241277e9ec56313c18b2a04f651 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 8 Nov 2024 10:43:05 +0100 Subject: [PATCH 1/5] buildClan: add empty default --- flakeModules/clan.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flakeModules/clan.nix b/flakeModules/clan.nix index b837d92dd..5b7850e27 100644 --- a/flakeModules/clan.nix +++ b/flakeModules/clan.nix @@ -13,6 +13,7 @@ in { options.clan = lib.mkOption { + default = { }; type = types.submoduleWith { specialArgs = { inherit clan-core self; From 4994b5acb51972a61bbe8ec73bf3ebaa22293b9b Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 8 Nov 2024 12:48:03 +0100 Subject: [PATCH 2/5] Inventory: init inventory.tags for globally defined static and dynamic tags --- lib/build-clan/computed-tags.nix | 33 ++ lib/build-clan/module.nix | 2 + lib/inventory/build-inventory/interface.nix | 81 +++- lib/jsonschema/default.nix | 29 +- lib/jsonschema/test_parseOption.nix | 471 +++++++++++--------- pkgs/clan-cli/clan_cli/inventory/classes.py | 1 + 6 files changed, 395 insertions(+), 222 deletions(-) create mode 100644 lib/build-clan/computed-tags.nix diff --git a/lib/build-clan/computed-tags.nix b/lib/build-clan/computed-tags.nix new file mode 100644 index 000000000..f3ec6a36d --- /dev/null +++ b/lib/build-clan/computed-tags.nix @@ -0,0 +1,33 @@ +{ + config, + lib, + ... +}: +{ + config.inventory = { + tags = ( + { machines, ... }: + { + # Only compute the default value + # The option MUST be defined in ./build-inventory/interface.nix + all = lib.mkDefault (builtins.attrNames machines); + } + ); + }; + # Add the computed tags to machine tags for displaying them + options.inventory = { + machines = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule ( + # 'name' is the machines attribute-name + { name, ... }: + { + tags = builtins.attrNames ( + lib.filterAttrs (_t: tagMemers: builtins.elem name tagMemers) config.inventory.tags + ); + } + ) + ); + }; + }; +} diff --git a/lib/build-clan/module.nix b/lib/build-clan/module.nix index bf8fbe7b0..079a7762c 100644 --- a/lib/build-clan/module.nix +++ b/lib/build-clan/module.nix @@ -162,6 +162,8 @@ in # # config.inventory.meta <- config.meta { inventory.meta = config.meta; } + # Set default for computed tags + ./computed-tags.nix ]; inherit nixosConfigurations; diff --git a/lib/inventory/build-inventory/interface.nix b/lib/inventory/build-inventory/interface.nix index 55fb875df..4e23ba8b3 100644 --- a/lib/inventory/build-inventory/interface.nix +++ b/lib/inventory/build-inventory/interface.nix @@ -1,4 +1,4 @@ -{ lib, ... }: +{ lib, config, ... }: let types = lib.types; @@ -88,7 +88,9 @@ let in { - imports = [ ./assertions.nix ]; + imports = [ + ./assertions.nix + ]; options = { assertions = lib.mkOption { type = types.listOf types.unspecified; @@ -103,6 +105,81 @@ in ]; }; }; + tags = lib.mkOption { + default = { }; + description = '' + Tags of the inventory are used to group machines together. + + It is recommended to use [`machine.tags`](#machinestags) to define the tags of the machines. + + This can be used to define custom tags that are either statically set or dynamically computed. + + #### Static Tags + + ???+ example "Static Tag Example" + ```nix + inventory.tags = { + foo = [ "machineA" "machineB" ]; + }; + ``` + + The tag `foo` will always be added to `machineA` and `machineB`. + + #### Dynamic Tags + + It is possible to compute tags based on the machines properties or based on other tags. + + !!! danger + This is a powerfull feature and should be used with caution. + + It is possible to cause infinite recursion by computing tags based on the machines properties or based on other tags. + + ???+ example "Dynamic Tag Example" + + allButFoo is a computed tag. It will be added to all machines except 'foo' + + `all` is a predefined tag. See the docs of [`tags.all`](#tagsall). + + ```nix + # inventory.tags ↓ ↓ inventory.machines + inventory.tags = {config, machines...}: { + # ↓↓↓ The "all" tag + allButFoo = builtins.filter (name: name != "foo") config.all; + }; + ``` + + !!! warning + Do NOT compute `tags` from `machine.tags` this will cause infinite recursion. + ''; + type = types.submoduleWith { + specialArgs = { + inherit (config) machines; + }; + modules = [ + { + freeformType = with lib.types; lazyAttrsOf (listOf str); + # Reserved tags + # Defined as options here to show them in advance + options = { + # 'All machines' tag + all = lib.mkOption { + type = with lib.types; listOf str; + defaultText = "[ ]"; + description = '' + !!! example "Predefined Tag" + + Will be added to all machines + + ```nix + inventory.machines.machineA.tags = [ "all" ]; + ``` + ''; + }; + }; + } + ]; + }; + }; machines = lib.mkOption { description = '' diff --git a/lib/jsonschema/default.nix b/lib/jsonschema/default.nix index 366e8238b..9ee155e4e 100644 --- a/lib/jsonschema/default.nix +++ b/lib/jsonschema/default.nix @@ -30,7 +30,10 @@ let filterExcluded = lib.filter (opt: !isExcludedOption opt); - filterExcludedAttrs = lib.filterAttrs (_name: opt: !isExcludedOption opt); + excludedOptionNames = [ "_freeformOptions" ]; + filterExcludedAttrs = lib.filterAttrs ( + name: opt: !isExcludedOption opt && !builtins.elem name excludedOptionNames + ); # Filter out options where the visible attribute is set to false filterInvisibleOpts = lib.filterAttrs (_name: opt: opt.visible or true); @@ -95,6 +98,27 @@ rec { requiredProps = lib.filterAttrs (_: prop: isRequired prop) properties; required = lib.optionalAttrs (requiredProps != { }) { required = lib.attrNames requiredProps; }; header' = if addHeader then header else { }; + + # freeformType is a special type + freeformDefs = (options._module.freeformType.definitions or [ ]); + checkFreeformDefs = + defs: + if (builtins.length defs) != 1 then + throw "parseOptions: freeformType definitions not supported" + else + defs; + # It seems that freeformType has [ null ] + freeformProperties = + if freeformDefs != [ ] && builtins.head freeformDefs != null then + # freeformType has only one definition + parseOption { + # options._module.freeformType.definitions + type = (builtins.head (checkFreeformDefs freeformDefs)); + _type = "option"; + loc = options._module.freeformType.loc; + } + else + { }; in # return jsonschema header' @@ -103,7 +127,8 @@ rec { type = "object"; inherit properties; additionalProperties = false; - }; + } + // freeformProperties; # parses and evaluated nixos option to a jsonschema property definition parseOption = diff --git a/lib/jsonschema/test_parseOption.nix b/lib/jsonschema/test_parseOption.nix index fc8abc7bc..bf1b62ef0 100644 --- a/lib/jsonschema/test_parseOption.nix +++ b/lib/jsonschema/test_parseOption.nix @@ -24,7 +24,6 @@ let in evaledConfig.options.opt; in - { testNoDefaultNoDescription = let @@ -210,6 +209,42 @@ in }; }; + testFreeFormOfInt = + let + default = { + foo = 1; + bar = 2; + }; + in + { + expr = slib.parseOptions (lib.evalModules { + modules = [ + { + freeformType = with lib.types; attrsOf int; + options = { + enable = lib.mkEnableOption "enable this"; + }; + } + default + ]; + }).options { }; + expected = { + "$schema" = "http://json-schema.org/draft-07/schema#"; + additionalProperties = { + type = "integer"; + }; + properties = { + enable = { + default = false; + description = "Whether to enable enable this."; + examples = [ true ]; + type = "boolean"; + }; + }; + type = "object"; + }; + }; + testLazyAttrsOfInt = let default = { @@ -229,230 +264,230 @@ in }; }; - testNullOrBool = - let - default = null; # null is a valid value for this type - in - { - expr = slib.parseOption (evalType (lib.types.nullOr lib.types.bool) default); - expected = { - oneOf = [ - { type = "null"; } - { type = "boolean"; } - ]; - inherit default description; - }; - }; + # testNullOrBool = + # let + # default = null; # null is a valid value for this type + # in + # { + # expr = slib.parseOption (evalType (lib.types.nullOr lib.types.bool) default); + # expected = { + # oneOf = [ + # { type = "null"; } + # { type = "boolean"; } + # ]; + # inherit default description; + # }; + # }; - testNullOrNullOr = - let - default = null; # null is a valid value for this type - in - { - expr = slib.parseOption (evalType (lib.types.nullOr (lib.types.nullOr lib.types.bool)) default); - expected = { - oneOf = [ - { type = "null"; } - { - oneOf = [ - { type = "null"; } - { type = "boolean"; } - ]; - } - ]; - inherit default description; - }; - }; + # testNullOrNullOr = + # let + # default = null; # null is a valid value for this type + # in + # { + # expr = slib.parseOption (evalType (lib.types.nullOr (lib.types.nullOr lib.types.bool)) default); + # expected = { + # oneOf = [ + # { type = "null"; } + # { + # oneOf = [ + # { type = "null"; } + # { type = "boolean"; } + # ]; + # } + # ]; + # inherit default description; + # }; + # }; - testSubmoduleOption = - let - subModule = { - options.opt = lib.mkOption { - type = lib.types.bool; - default = true; - inherit description; - }; - }; - in - { - expr = slib.parseOption (evalType (lib.types.submodule subModule) { }); - expected = { - type = "object"; - additionalProperties = false; - description = "Test Description"; - properties = { - opt = { - type = "boolean"; - default = true; - inherit description; - }; - }; - }; - }; + # testSubmoduleOption = + # let + # subModule = { + # options.opt = lib.mkOption { + # type = lib.types.bool; + # default = true; + # inherit description; + # }; + # }; + # in + # { + # expr = slib.parseOption (evalType (lib.types.submodule subModule) { }); + # expected = { + # type = "object"; + # additionalProperties = false; + # description = "Test Description"; + # properties = { + # opt = { + # type = "boolean"; + # default = true; + # inherit description; + # }; + # }; + # }; + # }; - testSubmoduleOptionWithoutDefault = - let - subModule = { - options.opt = lib.mkOption { - type = lib.types.bool; - inherit description; - }; - }; - in - { - expr = slib.parseOption (evalType (lib.types.submodule subModule) { }); - expected = { - type = "object"; - additionalProperties = false; - description = "Test Description"; - properties = { - opt = { - type = "boolean"; - inherit description; - }; - }; - required = [ "opt" ]; - }; - }; + # testSubmoduleOptionWithoutDefault = + # let + # subModule = { + # options.opt = lib.mkOption { + # type = lib.types.bool; + # inherit description; + # }; + # }; + # in + # { + # expr = slib.parseOption (evalType (lib.types.submodule subModule) { }); + # expected = { + # type = "object"; + # additionalProperties = false; + # description = "Test Description"; + # properties = { + # opt = { + # type = "boolean"; + # inherit description; + # }; + # }; + # required = [ "opt" ]; + # }; + # }; - testAttrsOfSubmodule = - let - subModule = { - options.opt = lib.mkOption { - type = lib.types.bool; - default = true; - inherit description; - }; - }; - default = { - foo.opt = false; - bar.opt = true; - }; - in - { - expr = slib.parseOption (evalType (lib.types.attrsOf (lib.types.submodule subModule)) default); - expected = { - type = "object"; - additionalProperties = { - type = "object"; - additionalProperties = false; - properties = { - opt = { - type = "boolean"; - default = true; - inherit description; - }; - }; - }; - inherit default description; - }; - }; + # testAttrsOfSubmodule = + # let + # subModule = { + # options.opt = lib.mkOption { + # type = lib.types.bool; + # default = true; + # inherit description; + # }; + # }; + # default = { + # foo.opt = false; + # bar.opt = true; + # }; + # in + # { + # expr = slib.parseOption (evalType (lib.types.attrsOf (lib.types.submodule subModule)) default); + # expected = { + # type = "object"; + # additionalProperties = { + # type = "object"; + # additionalProperties = false; + # properties = { + # opt = { + # type = "boolean"; + # default = true; + # inherit description; + # }; + # }; + # }; + # inherit default description; + # }; + # }; - testListOfSubmodule = - let - subModule = { - options.opt = lib.mkOption { - type = lib.types.bool; - default = true; - inherit description; - }; - }; - default = [ - { opt = false; } - { opt = true; } - ]; - in - { - expr = slib.parseOption (evalType (lib.types.listOf (lib.types.submodule subModule)) default); - expected = { - type = "array"; - items = { - type = "object"; - additionalProperties = false; - properties = { - opt = { - type = "boolean"; - default = true; - inherit description; - }; - }; - }; - inherit default description; - }; - }; + # testListOfSubmodule = + # let + # subModule = { + # options.opt = lib.mkOption { + # type = lib.types.bool; + # default = true; + # inherit description; + # }; + # }; + # default = [ + # { opt = false; } + # { opt = true; } + # ]; + # in + # { + # expr = slib.parseOption (evalType (lib.types.listOf (lib.types.submodule subModule)) default); + # expected = { + # type = "array"; + # items = { + # type = "object"; + # additionalProperties = false; + # properties = { + # opt = { + # type = "boolean"; + # default = true; + # inherit description; + # }; + # }; + # }; + # inherit default description; + # }; + # }; - testEither = - let - default = "foo"; - in - { - expr = slib.parseOption (evalType (lib.types.either lib.types.bool lib.types.str) default); - expected = { - oneOf = [ - { type = "boolean"; } - { type = "string"; } - ]; - inherit default description; - }; - }; + # testEither = + # let + # default = "foo"; + # in + # { + # expr = slib.parseOption (evalType (lib.types.either lib.types.bool lib.types.str) default); + # expected = { + # oneOf = [ + # { type = "boolean"; } + # { type = "string"; } + # ]; + # inherit default description; + # }; + # }; - testAnything = - let - default = "foo"; - in - { - expr = slib.parseOption (evalType lib.types.anything default); - expected = { - inherit default description; - type = [ - "boolean" - "integer" - "number" - "string" - "array" - "object" - "null" - ]; - }; - }; + # testAnything = + # let + # default = "foo"; + # in + # { + # expr = slib.parseOption (evalType lib.types.anything default); + # expected = { + # inherit default description; + # type = [ + # "boolean" + # "integer" + # "number" + # "string" + # "array" + # "object" + # "null" + # ]; + # }; + # }; - testUnspecified = - let - default = "foo"; - in - { - expr = slib.parseOption (evalType lib.types.unspecified default); - expected = { - inherit default description; - type = [ - "boolean" - "integer" - "number" - "string" - "array" - "object" - "null" - ]; - }; - }; + # testUnspecified = + # let + # default = "foo"; + # in + # { + # expr = slib.parseOption (evalType lib.types.unspecified default); + # expected = { + # inherit default description; + # type = [ + # "boolean" + # "integer" + # "number" + # "string" + # "array" + # "object" + # "null" + # ]; + # }; + # }; - testRaw = - let - default = "foo"; - in - { - expr = slib.parseOption (evalType lib.types.raw default); - expected = { - inherit default description; - type = [ - "boolean" - "integer" - "number" - "string" - "array" - "object" - "null" - ]; - }; - }; + # testRaw = + # let + # default = "foo"; + # in + # { + # expr = slib.parseOption (evalType lib.types.raw default); + # expected = { + # inherit default description; + # type = [ + # "boolean" + # "integer" + # "number" + # "string" + # "array" + # "object" + # "null" + # ]; + # }; + # }; } diff --git a/pkgs/clan-cli/clan_cli/inventory/classes.py b/pkgs/clan-cli/clan_cli/inventory/classes.py index d723d93a5..87d8f8483 100644 --- a/pkgs/clan-cli/clan_cli/inventory/classes.py +++ b/pkgs/clan-cli/clan_cli/inventory/classes.py @@ -36,3 +36,4 @@ class Inventory: meta: Meta machines: dict[str, Machine] = field(default_factory = dict) services: dict[str, Service] = field(default_factory = dict) + tags: dict[str, list[Any]] = field(default_factory = dict) From 16b0592d0fedcd2bd8190696fa84477582bee3c2 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 8 Nov 2024 13:20:45 +0100 Subject: [PATCH 3/5] Classgen: forward item types for array types --- pkgs/clan-cli/clan_cli/inventory/classes.py | 2 +- pkgs/classgen/main.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/inventory/classes.py b/pkgs/clan-cli/clan_cli/inventory/classes.py index 87d8f8483..296230db8 100644 --- a/pkgs/clan-cli/clan_cli/inventory/classes.py +++ b/pkgs/clan-cli/clan_cli/inventory/classes.py @@ -36,4 +36,4 @@ class Inventory: meta: Meta machines: dict[str, Machine] = field(default_factory = dict) services: dict[str, Service] = field(default_factory = dict) - tags: dict[str, list[Any]] = field(default_factory = dict) + tags: dict[str, list[str]] = field(default_factory = dict) diff --git a/pkgs/classgen/main.py b/pkgs/classgen/main.py index be0000bc0..92c56a65e 100644 --- a/pkgs/classgen/main.py +++ b/pkgs/classgen/main.py @@ -24,7 +24,10 @@ def map_json_type( res |= map_json_type(t) return res if isinstance(json_type, dict): - return map_json_type(json_type.get("type")) + items = json_type.get("items") + if items: + nested_types = map_json_type(items) + return map_json_type(json_type.get("type"), nested_types) if json_type == "string": return {"str"} if json_type == "integer": From a67f42ee7ad16f8a9b397994d12648477cd6d593 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 8 Nov 2024 13:22:58 +0100 Subject: [PATCH 4/5] Jsonschema: clean up tests --- lib/jsonschema/test_parseOption.nix | 470 +++++++++++++-------------- lib/jsonschema/test_parseOptions.nix | 37 +++ 2 files changed, 254 insertions(+), 253 deletions(-) diff --git a/lib/jsonschema/test_parseOption.nix b/lib/jsonschema/test_parseOption.nix index bf1b62ef0..896d47d89 100644 --- a/lib/jsonschema/test_parseOption.nix +++ b/lib/jsonschema/test_parseOption.nix @@ -209,42 +209,6 @@ in }; }; - testFreeFormOfInt = - let - default = { - foo = 1; - bar = 2; - }; - in - { - expr = slib.parseOptions (lib.evalModules { - modules = [ - { - freeformType = with lib.types; attrsOf int; - options = { - enable = lib.mkEnableOption "enable this"; - }; - } - default - ]; - }).options { }; - expected = { - "$schema" = "http://json-schema.org/draft-07/schema#"; - additionalProperties = { - type = "integer"; - }; - properties = { - enable = { - default = false; - description = "Whether to enable enable this."; - examples = [ true ]; - type = "boolean"; - }; - }; - type = "object"; - }; - }; - testLazyAttrsOfInt = let default = { @@ -264,230 +228,230 @@ in }; }; - # testNullOrBool = - # let - # default = null; # null is a valid value for this type - # in - # { - # expr = slib.parseOption (evalType (lib.types.nullOr lib.types.bool) default); - # expected = { - # oneOf = [ - # { type = "null"; } - # { type = "boolean"; } - # ]; - # inherit default description; - # }; - # }; + testNullOrBool = + let + default = null; # null is a valid value for this type + in + { + expr = slib.parseOption (evalType (lib.types.nullOr lib.types.bool) default); + expected = { + oneOf = [ + { type = "null"; } + { type = "boolean"; } + ]; + inherit default description; + }; + }; - # testNullOrNullOr = - # let - # default = null; # null is a valid value for this type - # in - # { - # expr = slib.parseOption (evalType (lib.types.nullOr (lib.types.nullOr lib.types.bool)) default); - # expected = { - # oneOf = [ - # { type = "null"; } - # { - # oneOf = [ - # { type = "null"; } - # { type = "boolean"; } - # ]; - # } - # ]; - # inherit default description; - # }; - # }; + testNullOrNullOr = + let + default = null; # null is a valid value for this type + in + { + expr = slib.parseOption (evalType (lib.types.nullOr (lib.types.nullOr lib.types.bool)) default); + expected = { + oneOf = [ + { type = "null"; } + { + oneOf = [ + { type = "null"; } + { type = "boolean"; } + ]; + } + ]; + inherit default description; + }; + }; - # testSubmoduleOption = - # let - # subModule = { - # options.opt = lib.mkOption { - # type = lib.types.bool; - # default = true; - # inherit description; - # }; - # }; - # in - # { - # expr = slib.parseOption (evalType (lib.types.submodule subModule) { }); - # expected = { - # type = "object"; - # additionalProperties = false; - # description = "Test Description"; - # properties = { - # opt = { - # type = "boolean"; - # default = true; - # inherit description; - # }; - # }; - # }; - # }; + testSubmoduleOption = + let + subModule = { + options.opt = lib.mkOption { + type = lib.types.bool; + default = true; + inherit description; + }; + }; + in + { + expr = slib.parseOption (evalType (lib.types.submodule subModule) { }); + expected = { + type = "object"; + additionalProperties = false; + description = "Test Description"; + properties = { + opt = { + type = "boolean"; + default = true; + inherit description; + }; + }; + }; + }; - # testSubmoduleOptionWithoutDefault = - # let - # subModule = { - # options.opt = lib.mkOption { - # type = lib.types.bool; - # inherit description; - # }; - # }; - # in - # { - # expr = slib.parseOption (evalType (lib.types.submodule subModule) { }); - # expected = { - # type = "object"; - # additionalProperties = false; - # description = "Test Description"; - # properties = { - # opt = { - # type = "boolean"; - # inherit description; - # }; - # }; - # required = [ "opt" ]; - # }; - # }; + testSubmoduleOptionWithoutDefault = + let + subModule = { + options.opt = lib.mkOption { + type = lib.types.bool; + inherit description; + }; + }; + in + { + expr = slib.parseOption (evalType (lib.types.submodule subModule) { }); + expected = { + type = "object"; + additionalProperties = false; + description = "Test Description"; + properties = { + opt = { + type = "boolean"; + inherit description; + }; + }; + required = [ "opt" ]; + }; + }; - # testAttrsOfSubmodule = - # let - # subModule = { - # options.opt = lib.mkOption { - # type = lib.types.bool; - # default = true; - # inherit description; - # }; - # }; - # default = { - # foo.opt = false; - # bar.opt = true; - # }; - # in - # { - # expr = slib.parseOption (evalType (lib.types.attrsOf (lib.types.submodule subModule)) default); - # expected = { - # type = "object"; - # additionalProperties = { - # type = "object"; - # additionalProperties = false; - # properties = { - # opt = { - # type = "boolean"; - # default = true; - # inherit description; - # }; - # }; - # }; - # inherit default description; - # }; - # }; + testAttrsOfSubmodule = + let + subModule = { + options.opt = lib.mkOption { + type = lib.types.bool; + default = true; + inherit description; + }; + }; + default = { + foo.opt = false; + bar.opt = true; + }; + in + { + expr = slib.parseOption (evalType (lib.types.attrsOf (lib.types.submodule subModule)) default); + expected = { + type = "object"; + additionalProperties = { + type = "object"; + additionalProperties = false; + properties = { + opt = { + type = "boolean"; + default = true; + inherit description; + }; + }; + }; + inherit default description; + }; + }; - # testListOfSubmodule = - # let - # subModule = { - # options.opt = lib.mkOption { - # type = lib.types.bool; - # default = true; - # inherit description; - # }; - # }; - # default = [ - # { opt = false; } - # { opt = true; } - # ]; - # in - # { - # expr = slib.parseOption (evalType (lib.types.listOf (lib.types.submodule subModule)) default); - # expected = { - # type = "array"; - # items = { - # type = "object"; - # additionalProperties = false; - # properties = { - # opt = { - # type = "boolean"; - # default = true; - # inherit description; - # }; - # }; - # }; - # inherit default description; - # }; - # }; + testListOfSubmodule = + let + subModule = { + options.opt = lib.mkOption { + type = lib.types.bool; + default = true; + inherit description; + }; + }; + default = [ + { opt = false; } + { opt = true; } + ]; + in + { + expr = slib.parseOption (evalType (lib.types.listOf (lib.types.submodule subModule)) default); + expected = { + type = "array"; + items = { + type = "object"; + additionalProperties = false; + properties = { + opt = { + type = "boolean"; + default = true; + inherit description; + }; + }; + }; + inherit default description; + }; + }; - # testEither = - # let - # default = "foo"; - # in - # { - # expr = slib.parseOption (evalType (lib.types.either lib.types.bool lib.types.str) default); - # expected = { - # oneOf = [ - # { type = "boolean"; } - # { type = "string"; } - # ]; - # inherit default description; - # }; - # }; + testEither = + let + default = "foo"; + in + { + expr = slib.parseOption (evalType (lib.types.either lib.types.bool lib.types.str) default); + expected = { + oneOf = [ + { type = "boolean"; } + { type = "string"; } + ]; + inherit default description; + }; + }; - # testAnything = - # let - # default = "foo"; - # in - # { - # expr = slib.parseOption (evalType lib.types.anything default); - # expected = { - # inherit default description; - # type = [ - # "boolean" - # "integer" - # "number" - # "string" - # "array" - # "object" - # "null" - # ]; - # }; - # }; + testAnything = + let + default = "foo"; + in + { + expr = slib.parseOption (evalType lib.types.anything default); + expected = { + inherit default description; + type = [ + "boolean" + "integer" + "number" + "string" + "array" + "object" + "null" + ]; + }; + }; - # testUnspecified = - # let - # default = "foo"; - # in - # { - # expr = slib.parseOption (evalType lib.types.unspecified default); - # expected = { - # inherit default description; - # type = [ - # "boolean" - # "integer" - # "number" - # "string" - # "array" - # "object" - # "null" - # ]; - # }; - # }; + testUnspecified = + let + default = "foo"; + in + { + expr = slib.parseOption (evalType lib.types.unspecified default); + expected = { + inherit default description; + type = [ + "boolean" + "integer" + "number" + "string" + "array" + "object" + "null" + ]; + }; + }; - # testRaw = - # let - # default = "foo"; - # in - # { - # expr = slib.parseOption (evalType lib.types.raw default); - # expected = { - # inherit default description; - # type = [ - # "boolean" - # "integer" - # "number" - # "string" - # "array" - # "object" - # "null" - # ]; - # }; - # }; + testRaw = + let + default = "foo"; + in + { + expr = slib.parseOption (evalType lib.types.raw default); + expected = { + inherit default description; + type = [ + "boolean" + "integer" + "number" + "string" + "array" + "object" + "null" + ]; + }; + }; } diff --git a/lib/jsonschema/test_parseOptions.nix b/lib/jsonschema/test_parseOptions.nix index f637999a3..fe5d2f169 100644 --- a/lib/jsonschema/test_parseOptions.nix +++ b/lib/jsonschema/test_parseOptions.nix @@ -36,4 +36,41 @@ type = "object"; }; }; + + testFreeFormOfInt = + let + default = { + foo = 1; + bar = 2; + }; + in + { + expr = slib.parseOptions (lib.evalModules { + modules = [ + { + freeformType = with lib.types; attrsOf int; + options = { + enable = lib.mkEnableOption "enable this"; + }; + } + default + ]; + }).options { }; + expected = { + "$schema" = "http://json-schema.org/draft-07/schema#"; + additionalProperties = { + type = "integer"; + }; + properties = { + enable = { + default = false; + description = "Whether to enable enable this."; + examples = [ true ]; + type = "boolean"; + }; + }; + type = "object"; + }; + }; + } From 46836496aa08340d82bdea8ca82704040cbb36f1 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 8 Nov 2024 13:54:14 +0100 Subject: [PATCH 5/5] Clan-ts-api: filter out reserved tags from typescript, library generates invalid types --- pkgs/clan-cli/flake-module.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/clan-cli/flake-module.nix b/pkgs/clan-cli/flake-module.nix index ccd4cc847..297c4ee8d 100644 --- a/pkgs/clan-cli/flake-module.nix +++ b/pkgs/clan-cli/flake-module.nix @@ -88,6 +88,7 @@ buildInputs = [ pkgs.python3 pkgs.json2ts + pkgs.jq ]; installPhase = '' @@ -98,7 +99,9 @@ json2ts --input $out/API.json > $out/API.ts # Retrieve python API Typescript types - json2ts --input ${self'.legacyPackages.schemas.inventory}/schema.json > $out/Inventory.ts + # delete the reserved tags from typechecking because the conversion library doesn't support them + jq 'del(.properties.tags.properties)' ${self'.legacyPackages.schemas.inventory}/schema.json > schema.json + json2ts --input schema.json > $out/Inventory.ts cp ${self'.legacyPackages.schemas.inventory}/* $out ''; };