clan-core/lib/jsonschema/default.nix

331 lines
9.6 KiB
Nix
Raw Normal View History

2024-03-17 18:48:49 +00:00
{
lib ? import <nixpkgs/lib>,
excludedTypes ? [
"functionTo"
"package"
2024-03-17 18:48:49 +00:00
],
}:
let
# remove _module attribute from options
clean = opts: builtins.removeAttrs opts [ "_module" ];
# throw error if option type is not supported
2024-03-17 18:48:49 +00:00
notSupported =
option:
lib.trace option throw ''
option type '${option.type.name}' ('${option.type.description}') not supported by jsonschema converter
location: ${lib.concatStringsSep "." option.loc}
'';
# Exclude the option if its type is in the excludedTypes list
# or if the option has a defaultText attribute
isExcludedOption =
option: ((lib.elem (option.type.name or null) excludedTypes) || (option ? defaultText));
2024-03-17 18:48:49 +00:00
filterExcluded = lib.filter (opt: !isExcludedOption opt);
2024-03-17 18:48:49 +00:00
filterExcludedAttrs = lib.filterAttrs (_name: opt: !isExcludedOption opt);
# Filter out options where the visible attribute is set to false
filterInvisibleOpts = lib.filterAttrs (_name: opt: opt.visible or true);
2024-03-17 18:48:49 +00:00
allBasicTypes = [
"boolean"
"integer"
"number"
"string"
"array"
"object"
"null"
];
in
rec {
# parses a nixos module to a jsonschema
2024-03-17 18:48:49 +00:00
parseModule =
module:
let
2024-03-17 18:48:49 +00:00
evaled = lib.evalModules { modules = [ module ]; };
in
{ "$schema" = "http://json-schema.org/draft-07/schema#"; } // parseOptions evaled.options;
# parses a set of evaluated nixos options to a jsonschema
2024-03-17 18:48:49 +00:00
parseOptions =
options':
let
options = filterInvisibleOpts (filterExcludedAttrs (clean options'));
# parse options to jsonschema properties
properties = lib.mapAttrs (_name: option: parseOption option) options;
# TODO: figure out how to handle if prop.anyOf is used
2024-03-17 18:48:49 +00:00
isRequired = prop: !(prop ? default || prop.type or null == "object");
requiredProps = lib.filterAttrs (_: prop: isRequired prop) properties;
2024-03-17 18:48:49 +00:00
required = lib.optionalAttrs (requiredProps != { }) { required = lib.attrNames requiredProps; };
in
# return jsonschema
2024-03-17 18:48:49 +00:00
required
// {
type = "object";
inherit properties;
additionalProperties = false;
};
# parses and evaluated nixos option to a jsonschema property definition
2024-03-17 18:48:49 +00:00
parseOption =
option:
let
2024-03-17 18:48:49 +00:00
default = lib.optionalAttrs (option ? default) { inherit (option) default; };
example = lib.optionalAttrs (option ? example) {
examples =
if (builtins.typeOf option.example) == "list" then option.example else [ option.example ];
};
description = lib.optionalAttrs (option ? description) {
2023-11-21 10:29:06 +00:00
description = option.description.text or option.description;
};
in
2023-08-09 16:19:36 +00:00
# either type
2024-05-23 14:11:54 +00:00
# TODO: if all nested options are excluded, the parent should be excluded too
2024-03-17 18:48:49 +00:00
if
2024-05-23 14:11:54 +00:00
option.type.name or null == "either" || option.type.name or null == "coercedTo"
# return jsonschema property definition for either
then
let
optionsList' = [
2024-03-17 18:48:49 +00:00
{
2024-05-23 14:11:54 +00:00
type = option.type.nestedTypes.left or option.type.nestedTypes.coercedType;
2024-03-17 18:48:49 +00:00
_type = "option";
loc = option.loc;
}
{
2024-05-23 14:11:54 +00:00
type = option.type.nestedTypes.right or option.type.nestedTypes.finalType;
2024-03-17 18:48:49 +00:00
_type = "option";
loc = option.loc;
}
];
optionsList = filterExcluded optionsList';
in
default // example // description // { anyOf = map parseOption optionsList; }
2023-08-09 16:19:36 +00:00
# handle nested options (not a submodule)
2024-03-17 18:48:49 +00:00
else if !option ? _type then
parseOptions option
2023-08-09 16:19:36 +00:00
# throw if not an option
2024-03-17 18:48:49 +00:00
else if option._type != "option" && option._type != "option-type" then
throw "parseOption: not an option"
# parse nullOr
2024-03-17 18:48:49 +00:00
else if
option.type.name == "nullOr"
# return jsonschema property definition for nullOr
then
let
2024-03-17 18:48:49 +00:00
nestedOption = {
type = option.type.nestedTypes.elemType;
_type = "option";
loc = option.loc;
};
in
2024-03-17 18:48:49 +00:00
default
// example
2024-03-17 18:48:49 +00:00
// description
// {
anyOf = [
{ type = "null"; }
] ++ (lib.optional (!isExcludedOption nestedOption) (parseOption nestedOption));
}
# parse bool
2024-03-17 18:48:49 +00:00
else if
option.type.name == "bool"
# return jsonschema property definition for bool
2024-03-17 18:48:49 +00:00
then
default // example // description // { type = "boolean"; }
# parse float
2024-03-17 18:48:49 +00:00
else if
option.type.name == "float"
# return jsonschema property definition for float
2024-03-17 18:48:49 +00:00
then
default // example // description // { type = "number"; }
# parse int
2024-03-17 18:48:49 +00:00
else if
(option.type.name == "int" || option.type.name == "positiveInt")
# return jsonschema property definition for int
2024-03-17 18:48:49 +00:00
then
default // example // description // { type = "integer"; }
# TODO: Add support for intMatching in jsonschema
# parse port type aka. "unsignedInt16"
2024-05-23 14:11:54 +00:00
else if
option.type.name == "unsignedInt16"
|| option.type.name == "unsignedInt"
|| option.type.name == "pkcs11"
|| option.type.name == "intBetween"
then
default // example // description // { type = "integer"; }
# parse string
2024-05-23 14:11:54 +00:00
# TODO: parse more precise string types
2024-03-17 18:48:49 +00:00
else if
option.type.name == "str"
2024-05-23 14:11:54 +00:00
|| option.type.name == "singleLineStr"
|| option.type.name == "passwdEntry str"
|| option.type.name == "passwdEntry path"
# return jsonschema property definition for string
2024-03-17 18:48:49 +00:00
then
default // example // description // { type = "string"; }
# TODO: Add support for stringMatching in jsonschema
# parse stringMatching
else if lib.strings.hasPrefix "strMatching" option.type.name then
default // example // description // { type = "string"; }
# TODO: Add support for separatedString in jsonschema
else if lib.strings.hasPrefix "separatedString" option.type.name then
default // example // description // { type = "string"; }
2023-09-14 08:37:16 +00:00
# parse string
2024-03-17 18:48:49 +00:00
else if
option.type.name == "path"
2023-09-14 08:37:16 +00:00
# return jsonschema property definition for path
2024-03-17 18:48:49 +00:00
then
default // example // description // { type = "string"; }
2023-09-14 08:37:16 +00:00
# parse anything
2024-03-17 18:48:49 +00:00
else if
option.type.name == "anything"
# return jsonschema property definition for anything
2024-03-17 18:48:49 +00:00
then
default // example // description // { type = allBasicTypes; }
# parse unspecified
2024-03-17 18:48:49 +00:00
else if
option.type.name == "unspecified"
# return jsonschema property definition for unspecified
2024-03-17 18:48:49 +00:00
then
default // example // description // { type = allBasicTypes; }
# parse raw
2024-03-17 18:48:49 +00:00
else if
option.type.name == "raw"
# return jsonschema property definition for raw
2024-03-17 18:48:49 +00:00
then
default // example // description // { type = allBasicTypes; }
# parse enum
2024-03-17 18:48:49 +00:00
else if
option.type.name == "enum"
# return jsonschema property definition for enum
2024-03-17 18:48:49 +00:00
then
default // example // description // { enum = option.type.functor.payload; }
# parse listOf submodule
2024-03-17 18:48:49 +00:00
else if
option.type.name == "listOf" && option.type.functor.wrapped.name == "submodule"
# return jsonschema property definition for listOf submodule
2024-03-17 18:48:49 +00:00
then
default
// example
2024-03-17 18:48:49 +00:00
// description
// {
type = "array";
items = parseOptions (option.type.functor.wrapped.getSubOptions option.loc);
}
# parse list
2024-03-17 18:48:49 +00:00
else if
(option.type.name == "listOf")
# return jsonschema property definition for list
then
let
2024-03-17 18:48:49 +00:00
nestedOption = {
type = option.type.functor.wrapped;
_type = "option";
loc = option.loc;
};
in
2024-03-17 18:48:49 +00:00
default
// example
2024-03-17 18:48:49 +00:00
// description
// {
type = "array";
}
2024-03-17 18:48:49 +00:00
// (lib.optionalAttrs (!isExcludedOption nestedOption) { items = parseOption nestedOption; })
# parse list of unspecified
else if
2024-03-17 18:48:49 +00:00
(option.type.name == "listOf") && (option.type.functor.wrapped.name == "unspecified")
# return jsonschema property definition for list
2024-03-17 18:48:49 +00:00
then
default // example // description // { type = "array"; }
# parse attrsOf submodule
2024-03-17 18:48:49 +00:00
else if
option.type.name == "attrsOf" && option.type.nestedTypes.elemType.name == "submodule"
# return jsonschema property definition for attrsOf submodule
2024-03-17 18:48:49 +00:00
then
default
// example
2024-03-17 18:48:49 +00:00
// description
// {
type = "object";
additionalProperties = parseOptions (option.type.nestedTypes.elemType.getSubOptions option.loc);
}
# parse attrs
2024-03-17 18:48:49 +00:00
else if
option.type.name == "attrs"
# return jsonschema property definition for attrs
2024-03-17 18:48:49 +00:00
then
default
// example
2024-03-17 18:48:49 +00:00
// description
// {
type = "object";
additionalProperties = true;
}
# parse attrsOf
# TODO: if nested option is excluded, the parent sould be excluded too
2024-03-17 18:48:49 +00:00
else if
option.type.name == "attrsOf" || option.type.name == "lazyAttrsOf"
# return jsonschema property definition for attrs
then
let
2024-03-17 18:48:49 +00:00
nestedOption = {
type = option.type.nestedTypes.elemType;
_type = "option";
loc = option.loc;
};
in
2024-03-17 18:48:49 +00:00
default
// example
2024-03-17 18:48:49 +00:00
// description
// {
type = "object";
additionalProperties =
2024-03-17 18:48:49 +00:00
if !isExcludedOption nestedOption then
parseOption {
type = option.type.nestedTypes.elemType;
_type = "option";
loc = option.loc;
}
else
false;
}
# parse submodule
2024-03-17 18:48:49 +00:00
else if
option.type.name == "submodule"
# return jsonschema property definition for submodule
# then (lib.attrNames (option.type.getSubOptions option.loc).opt)
2024-03-17 18:48:49 +00:00
then
parseOptions (option.type.getSubOptions option.loc)
# throw error if option type is not supported
2024-03-17 18:48:49 +00:00
else
notSupported option;
}