Json-schema: extend interface by header to allow schema spec and arbitrary extensions

This commit is contained in:
Johannes Kirschbauer
2024-07-14 10:44:42 +02:00
parent 76a6111764
commit 77577a9f27
6 changed files with 26 additions and 77 deletions

View File

@@ -1,2 +0,0 @@
module: "clan.lol/inventory"
language: version: "v0.8.2"

View File

@@ -1,23 +0,0 @@
package inventory
import (
"clan.lol/inventory/schema"
)
@jsonschema(schema="http://json-schema.org/schema#")
#Root: {
meta: {
// A name of the clan (primarily shown by the UI)
name: string
// A description of the clan
description?: string
// The icon path
icon?: string
}
// // A map of services
schema.#service
// // A map of machines
schema.#machine
}

View File

@@ -1,40 +0,0 @@
package schema
#machine: machines: [string]: {
name: string,
description?: string,
icon?: string
tags: [...string]
system?: string
}
#role: string
#service: services: [string]: [string]: {
// Required meta fields
meta: {
name: string,
icon?: string
description?: string,
},
// We moved the machine sepcific config to "machines".
// It may be moved back depending on what makes more sense in the future.
roles: [#role]: {
machines: [...string],
tags: [...string],
}
machines?: {
[string]: {
config?: {
...
}
}
},
// Global Configuration for the service
config?: {
// Schema depends on the module.
// It declares the interface how the service can be configured.
...
}
}

View File

@@ -47,22 +47,35 @@ rec {
let
evaled = lib.evalModules { modules = [ module ]; };
in
{ "$schema" = "http://json-schema.org/draft-07/schema#"; } // parseOptions evaled.options;
parseOptions evaled.options { };
parseOptions' = lib.flip parseOptions { addHeader = false; };
# parses a set of evaluated nixos options to a jsonschema
parseOptions =
options':
options:
{
# The top-level header object should specify at least the schema version
# Can be customized if needed
header ? {
"$schema" = "http://json-schema.org/draft-07/schema#";
},
# By default the header is not added to the schema
addHeader ? true,
}:
let
options = filterInvisibleOpts (filterExcludedAttrs (clean options'));
options' = filterInvisibleOpts (filterExcludedAttrs (clean options));
# parse options to jsonschema properties
properties = lib.mapAttrs (_name: option: parseOption option) options;
properties = lib.mapAttrs (_name: option: parseOption option) options';
# TODO: figure out how to handle if prop.anyOf is used
isRequired = prop: !(prop ? default || prop.type or null == "object");
requiredProps = lib.filterAttrs (_: prop: isRequired prop) properties;
required = lib.optionalAttrs (requiredProps != { }) { required = lib.attrNames requiredProps; };
header' = if addHeader then header else { };
in
# return jsonschema
required
header'
// required
// {
type = "object";
inherit properties;
@@ -108,7 +121,7 @@ rec {
# handle nested options (not a submodule)
else if !option ? _type then
parseOptions option
parseOptions' option
# throw if not an option
else if option._type != "option" && option._type != "option-type" then
@@ -231,7 +244,7 @@ rec {
// description
// {
type = "array";
items = parseOptions (option.type.functor.wrapped.getSubOptions option.loc);
items = parseOptions' (option.type.functor.wrapped.getSubOptions option.loc);
}
# parse list
@@ -271,7 +284,7 @@ rec {
// description
// {
type = "object";
additionalProperties = parseOptions (option.type.nestedTypes.elemType.getSubOptions option.loc);
additionalProperties = parseOptions' (option.type.nestedTypes.elemType.getSubOptions option.loc);
}
# parse attrs
@@ -322,7 +335,7 @@ rec {
# return jsonschema property definition for submodule
# then (lib.attrNames (option.type.getSubOptions option.loc).opt)
then
parseOptions (option.type.getSubOptions option.loc)
parseOptions' (option.type.getSubOptions option.loc)
# throw error if option type is not supported
else

View File

@@ -17,8 +17,9 @@
};
in
{
expr = slib.parseOptions evaled.options;
expr = slib.parseOptions evaled.options { };
expected = {
"$schema" = "http://json-schema.org/draft-07/schema#";
additionalProperties = false;
properties = {
foo = {

View File

@@ -18,13 +18,13 @@
if (eval.options.clan ? "${mName}") then eval.options.clan.${mName} else { };
clanModuleSchemas = lib.mapAttrs (
modulename: _: self.lib.jsonschema.parseOptions (optionsFromModule modulename)
modulename: _: self.lib.jsonschema.parseOptions (optionsFromModule modulename) { }
) clanModules;
clanModuleFunctionSchemas = lib.mapAttrsFlatten (modulename: _: {
name = modulename;
description = self.lib.modules.getShortDescription modulename;
parameters = self.lib.jsonschema.parseOptions (optionsFromModule modulename);
parameters = self.lib.jsonschema.parseOptions (optionsFromModule modulename) { };
}) clanModules;
in
rec {