Merge pull request 'Feat(clanLib): init types {uniqueDeferredSerializableModule}' (#3737) from hsjobeki/clan-core:fix-2 into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3737
This commit is contained in:
hsjobeki
2025-05-21 18:50:42 +00:00
13 changed files with 183 additions and 11 deletions

View File

@@ -3,12 +3,13 @@
## Add any logic to ./module.nix ## Add any logic to ./module.nix
{ {
lib, lib,
clanLib,
... ...
}: }:
{ {
flakePartsModule = { flakePartsModule = {
imports = [ imports = [
./interface.nix (lib.modules.importApply ./interface.nix { inherit clanLib; })
./module.nix ./module.nix
]; ];
}; };

View File

@@ -1,9 +1,13 @@
{ pkgs, lib }: {
pkgs,
lib,
clanLib,
}:
let let
eval = lib.evalModules { eval = lib.evalModules {
class = "nixos"; class = "nixos";
modules = [ modules = [
./interface.nix (lib.modules.importApply ./interface.nix { inherit clanLib; })
]; ];
}; };
evalDocs = pkgs.nixosOptionsDoc { evalDocs = pkgs.nixosOptionsDoc {

View File

@@ -19,6 +19,7 @@ in
let let
jsonDocs = import ./eval-docs.nix { jsonDocs = import ./eval-docs.nix {
inherit pkgs lib; inherit pkgs lib;
inherit (self) clanLib;
}; };
in in
{ {

View File

@@ -18,7 +18,7 @@ module:
; ;
}; };
modules = [ modules = [
./interface.nix (lib.modules.importApply ./interface.nix { inherit (clan-core) clanLib; })
module module
{ {
inherit specialArgs; inherit specialArgs;

View File

@@ -1,3 +1,4 @@
{ clanLib }:
{ {
lib, lib,
self, self,
@@ -94,7 +95,11 @@ in
}; };
inventory = lib.mkOption { inventory = lib.mkOption {
type = types.submodule { imports = [ ../inventory/build-inventory/interface.nix ]; }; type = types.submodule {
imports = [
(lib.modules.importApply ../inventory/build-inventory/interface.nix { inherit clanLib; })
];
};
description = '' description = ''
The `Inventory` submodule. The `Inventory` submodule.

View File

@@ -43,6 +43,8 @@ lib.fix (clanLib: {
inventory = clanLib.callLib ./inventory { }; inventory = clanLib.callLib ./inventory { };
modules = clanLib.callLib ./inventory/frontmatter { }; modules = clanLib.callLib ./inventory/frontmatter { };
test = clanLib.callLib ./test { }; test = clanLib.callLib ./test { };
# Custom types
types = clanLib.callLib ./types { };
# Plain imports. # Plain imports.
introspection = import ./introspection { inherit lib; }; introspection = import ./introspection { inherit lib; };

View File

@@ -11,6 +11,7 @@ rec {
./introspection/flake-module.nix ./introspection/flake-module.nix
./inventory/flake-module.nix ./inventory/flake-module.nix
./jsonschema/flake-module.nix ./jsonschema/flake-module.nix
./types/flake-module.nix
]; ];
flake.clanLib = import ./default.nix { flake.clanLib = import ./default.nix {
inherit lib inputs self; inherit lib inputs self;

View File

@@ -1,3 +1,4 @@
{ clanLib }:
{ {
lib, lib,
config, config,
@@ -390,9 +391,7 @@ in
types.submodule { types.submodule {
options.settings = lib.mkOption { options.settings = lib.mkOption {
default = { }; default = { };
# Dont transform the value with `types.deferredModule` here. We need to keep it json serializable type = clanLib.types.uniqueDeferredSerializableModule;
# TODO: We need a custom serializer for deferredModule
type = types.deferredModule;
}; };
} }
); );
@@ -404,7 +403,7 @@ in
}; };
settings = lib.mkOption { settings = lib.mkOption {
default = { }; default = { };
type = types.deferredModule; type = clanLib.types.uniqueDeferredSerializableModule;
}; };
}; };
} }

View File

@@ -5,7 +5,7 @@ in
{ {
inherit (services) evalClanService mapInstances resolveModule; inherit (services) evalClanService mapInstances resolveModule;
inherit (import ./build-inventory { inherit lib clanLib; }) buildInventory; inherit (import ./build-inventory { inherit lib clanLib; }) buildInventory;
interface = ./build-inventory/interface.nix; interface = lib.modules.importApply ./build-inventory/interface.nix { inherit clanLib; };
# Returns the list of machine names # Returns the list of machine names
# { ... } -> [ string ] # { ... } -> [ string ]
resolveTags = resolveTags =

View File

@@ -17,7 +17,9 @@ let
frontMatterSchema = jsonLib.parseOptions self.clanLib.modules.frontmatterOptions { }; frontMatterSchema = jsonLib.parseOptions self.clanLib.modules.frontmatterOptions { };
inventorySchema = jsonLib.parseModule (import ../build-inventory/interface.nix); inventorySchema = jsonLib.parseModule (
import ../build-inventory/interface.nix { inherit (self) clanLib; }
);
renderSchema = pkgs.writers.writePython3Bin "render-schema" { renderSchema = pkgs.writers.writePython3Bin "render-schema" {
flakeIgnore = [ flakeIgnore = [

40
lib/types/default.nix Normal file
View File

@@ -0,0 +1,40 @@
{ lib, ... }:
{
uniqueDeferredSerializableModule = lib.fix (
self:
let
checkDef =
_loc: def:
if def.value ? imports then
throw "uniqueDeferredSerializableModule doesn't allow nested imports"
else
def;
in
# Essentially the "raw" type, but with a custom name and check
lib.mkOptionType {
name = "deferredModule";
description = "deferred custom module. Must be JSON serializable.";
descriptionClass = "noun";
# Unfortunately, tryEval doesn't catch JSON errors
check = value: lib.seq (builtins.toJSON value) (lib.isAttrs value);
merge = lib.options.mergeUniqueOption {
message = "------";
merge = loc: defs: {
imports = map (
def:
lib.seq (checkDef loc def) lib.setDefaultModuleLocation
"${def.file}, via option ${lib.showOption loc}"
def.value
) defs;
};
};
functor = {
inherit (self) name;
type = self;
# Non mergable type
binOp = _a: _b: null;
};
}
);
}

View File

@@ -0,0 +1,25 @@
{ self, inputs, ... }:
{
perSystem =
{ ... }:
let
# Module that contains the tests
# This module adds:
# - legacyPackages.<system>.eval-tests-hello-world
# - checks.<system>.eval-tests-hello-world
test-types-module = (
self.clanLib.test.flakeModules.makeEvalChecks {
module = throw "";
inherit self inputs;
testName = "types";
tests = ./tests.nix;
# Optional arguments passed to the test
testArgs = { };
}
);
in
{
imports = [ test-types-module ];
legacyPackages.xxx = { };
};
}

92
lib/types/tests.nix Normal file
View File

@@ -0,0 +1,92 @@
{ lib, clanLib, ... }:
let
evalSettingsModule =
m:
lib.evalModules {
modules = [
{
options.foo = lib.mkOption {
type = clanLib.types.uniqueDeferredSerializableModule;
};
}
m
];
};
in
{
test_simple =
let
eval = evalSettingsModule {
foo = { };
};
in
{
inherit eval;
expr = eval.config.foo;
expected = {
# Foo has imports
# This can only ever be one module due to the type of foo
imports = [
{
# This is the result of 'setDefaultModuleLocation'
# Which also returns exactly one module
_file = "<unknown-file>, via option foo";
imports = [
{ }
];
}
];
};
};
test_no_nested_imports =
let
eval = evalSettingsModule {
foo = {
imports = [ ];
};
};
in
{
inherit eval;
expr = eval.config.foo;
expectedError = {
type = "ThrownError";
message = "*nested imports";
};
};
test_no_function_modules =
let
eval = evalSettingsModule {
foo =
{ ... }:
{
};
};
in
{
inherit eval;
expr = eval.config.foo;
expectedError = {
type = "TypeError";
message = "cannot convert a function to JSON";
};
};
test_non_attrs_module =
let
eval = evalSettingsModule {
foo = "foo.nix";
};
in
{
inherit eval;
expr = eval.config.foo;
expectedError = {
type = "ThrownError";
message = ".*foo.* is not of type";
};
};
}