Feat(clan.service): require roles.interface to be json serializable

This commit is contained in:
Johannes Kirschbauer
2025-04-29 14:37:27 +02:00
parent 78f96ec533
commit 9b5f100ac6
2 changed files with 31 additions and 25 deletions

View File

@@ -1,7 +1,7 @@
# This module enables itself if # This module enables itself if
# manifest.features.API = true # manifest.features.API = true
# It converts the roles.interface to a json-schema # It converts the roles.interface to a json-schema
{ clanLib }: { clanLib, attrName }:
let let
converter = clanLib.jsonschema { converter = clanLib.jsonschema {
includeDefaults = true; includeDefaults = true;
@@ -11,38 +11,41 @@ in
{ {
options.result.api = lib.mkOption { options.result.api = lib.mkOption {
default = { }; default = { };
type = lib.types.submodule ( type = lib.types.submodule ({
lib.optionalAttrs config.manifest.features.API { options.schema = lib.mkOption {
options.schema = lib.mkOption { description = ''
description = '' The API schema for configuring the service.
The API schema for configuring the service.
Each 'role.<name>.interface' is converted to a json-schema. Each 'role.<name>.interface' is converted to a json-schema.
This can be used to generate and type check the API relevant objects. This can be used to generate and type check the API relevant objects.
''; '';
defaultText = lib.literalExpression '' defaultText = lib.literalExpression ''
{ {
peer = { $schema" = "http://json-schema.org/draft-07/schema#"; ... } peer = { $schema" = "http://json-schema.org/draft-07/schema#"; ... }
commuter = { $schema" = "http://json-schema.org/draft-07/schema#"; ... } commuter = { $schema" = "http://json-schema.org/draft-07/schema#"; ... }
distributor = { $schema" = "http://json-schema.org/draft-07/schema#"; ... } distributor = { $schema" = "http://json-schema.org/draft-07/schema#"; ... }
} }
''; '';
default = lib.mapAttrs (_roleName: v: converter.parseModule v.interface) config.roles; default = lib.mapAttrs (_roleName: v: converter.parseModule v.interface) config.roles;
}; };
} });
);
}; };
config.result.assertions = lib.optionalAttrs (config.manifest.features.API) ( config.result.assertions = (
lib.mapAttrs' (roleName: _role: { lib.mapAttrs' (roleName: _role: {
name = "${roleName}"; name = "${roleName}";
value = { value = {
# TODO: make the path to access the schema shorter
message = '' message = ''
`roles.${roleName}.interface` is not JSON serializable. `roles.${roleName}.interface` is not JSON serializable.
but 'manifest.features.API' is enabled, which requires all 'roles-interfaces' of this module to be a subset of JSON. 'clan.services' modules require all 'roles.*.interfaces' to be subset of JSON.
clan.service module '${config.manifest.name} : clan.service module '${config.manifest.name}
To see the evaluation problem run
nix eval .#clanInternals.inventoryClass.distributedServices.importedModulesEvaluated.${attrName}.config.result.api.schema.${roleName}
''; '';
assertion = (builtins.tryEval (lib.deepSeq config.result.api.schema.${roleName} true)).success; assertion = (builtins.tryEval (lib.deepSeq config.result.api.schema.${roleName} true)).success;
}; };

View File

@@ -118,7 +118,7 @@ let
# TODO: Eagerly check the _class of the resolved module # TODO: Eagerly check the _class of the resolved module
importedModulesEvaluated = lib.mapAttrs ( importedModulesEvaluated = lib.mapAttrs (
_module_ident: instances: module_ident: instances:
(lib.evalModules { (lib.evalModules {
class = "clan.service"; class = "clan.service";
modules = modules =
@@ -128,7 +128,10 @@ let
(builtins.head instances).instance.resolvedModule (builtins.head instances).instance.resolvedModule
# feature modules # feature modules
(lib.modules.importApply ./api-feature.nix { inherit clanLib; }) (lib.modules.importApply ./api-feature.nix {
inherit clanLib;
attrName = module_ident;
})
] ]
# Include all the instances that correlate to the resolved module # Include all the instances that correlate to the resolved module
++ (builtins.map (v: { ++ (builtins.map (v: {