From a058c36804fe8e178de044d2f9db8e868a19cd99 Mon Sep 17 00:00:00 2001 From: Sebastian Schleemilch Date: Mon, 25 Nov 2024 00:03:02 +0100 Subject: [PATCH] status quo --- src/vss_tools/exporters/jsonschema.py | 213 ++++++++++---------------- 1 file changed, 77 insertions(+), 136 deletions(-) diff --git a/src/vss_tools/exporters/jsonschema.py b/src/vss_tools/exporters/jsonschema.py index d1974527..8c95942e 100644 --- a/src/vss_tools/exporters/jsonschema.py +++ b/src/vss_tools/exporters/jsonschema.py @@ -17,114 +17,31 @@ import vss_tools.cli_options as clo from vss_tools import log +from vss_tools.datatypes import Datatypes, is_array, resolve_datatype from vss_tools.main import get_trees from vss_tools.model import VSSDataBranch, VSSDataDatatype, VSSDataStruct from vss_tools.tree import VSSNode -type_map = { - "int8": "integer", - "uint8": "integer", - "int16": "integer", - "uint16": "integer", - "int32": "integer", - "uint32": "integer", - "int64": "integer", - "uint64": "integer", - "boolean": "boolean", - "float": "number", - "double": "number", - "string": "string", - "int8[]": "array", - "uint8[]": "array", - "int16[]": "array", - "uint16[]": "array", - "int32[]": "array", - "uint32[]": "array", - "int64[]": "array", - "uint64[]": "array", - "boolean[]": "array", - "float[]": "array", - "double[]": "array", - "string[]": "array", -} +class VSSJSONSchemaExporterException(Exception): + pass -def export_node( - json_dict, - node: VSSNode, - all_extended_attributes: bool, - no_additional_properties: bool, - require_all_properties: bool, -): - """Preparing nodes for JSON schema output.""" - # keyword with X- sign are left for extensions and they are not part of official JSON schema - data = node.get_vss_data() - json_dict[node.name] = { - "description": data.description, - } - - if isinstance(data, VSSDataDatatype): - json_dict[node.name]["type"] = type_map[data.datatype] - - min = getattr(data, "min", None) - if min is not None: - json_dict[node.name]["minimum"] = min - - max = getattr(data, "max", None) - if max is not None: - json_dict[node.name]["maximum"] = max - - allowed = getattr(data, "allowed", None) - if allowed: - json_dict[node.name]["enum"] = allowed - - default = getattr(data, "default", None) - if default: - json_dict[node.name]["default"] = default - - if isinstance(data, VSSDataStruct): - json_dict[node.name]["type"] = "object" - - if all_extended_attributes: - json_dict[node.name]["x-VSStype"] = data.type.value - datatype = getattr(data, "datatype", None) - if datatype: - json_dict[node.name]["x-datatype"] = datatype - if data.deprecation: - json_dict[node.name]["x-deprecation"] = data.deprecation - - # in case of unit or aggregate, the attribute will be missing - unit = getattr(data, "unit", None) - if unit: - json_dict[node.name]["x-unit"] = unit - - aggregate = getattr(data, "aggregate", None) - if aggregate: - json_dict[node.name]["x-aggregate"] = aggregate - if aggregate: - json_dict[node.name]["type"] = "object" - - if data.comment: - json_dict[node.name]["x-comment"] = data.comment - - for field in data.get_extra_attributes(): - json_dict[node.name][field] = getattr(data, field) - - # Generate child nodes - if isinstance(data, VSSDataBranch) or isinstance(node.data, VSSDataStruct): - if no_additional_properties: - json_dict[node.name]["additionalProperties"] = False - json_dict[node.name]["properties"] = {} - if require_all_properties: - json_dict[node.name]["required"] = [child.name for child in node.children] - for child in node.children: - export_node( - json_dict[node.name]["properties"], - child, - all_extended_attributes, - no_additional_properties, - require_all_properties, - ) + +type_map = { + Datatypes.INT8[0]: ("integer", -128, 127), + Datatypes.UINT8[0]: ("integer", 0, 255), + Datatypes.INT16[0]: ("integer", -32768, 32767), + Datatypes.UINT16[0]: ("integer", 0, 65535), + Datatypes.INT32[0]: ("integer", -2147483648, 2147483647), + Datatypes.UINT32[0]: ("integer", 0, 4294967295), + Datatypes.INT64[0]: ("integer"), + Datatypes.UINT64[0]: ("integer"), + Datatypes.FLOAT[0]: ("number",), + Datatypes.DOUBLE[0]: ("number",), + Datatypes.NUMERIC[0]: ("number",), + Datatypes.BOOLEAN[0]: ("boolean",), + Datatypes.STRING[0]: ("string",), +} @click.command() @@ -189,39 +106,63 @@ def cli( log.info("Serializing pretty JSON schema...") indent = 2 - signals_json_schema: Dict[str, Any] = {} - export_node( - signals_json_schema, - tree, - extend_all_attributes, - no_additional_properties, - require_all_properties, - ) + schema = {"$schema": "https://json-schema.org/draft/2020-12/schema", "title": tree.name} + + add_node(schema, tree, datatype_tree, no_additional_properties, require_all_properties) - # Add data types to the schema - if datatype_tree is not None: - data_types_json_schema: Dict[str, Any] = {} - export_node( - data_types_json_schema, - datatype_tree, - extend_all_attributes, - no_additional_properties, - require_all_properties, - ) - if extend_all_attributes: - signals_json_schema["x-ComplexDataTypes"] = data_types_json_schema - - # VSS models only have one root, so there should only be one - # key in the dict - assert len(signals_json_schema.keys()) == 1 - top_node_name = list(signals_json_schema.keys())[0] - signals_json_schema = signals_json_schema.pop(top_node_name) - - json_schema = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": top_node_name, - "type": "object", - **signals_json_schema, - } with open(output, "w", encoding="utf-8") as output_file: - json.dump(json_schema, output_file, indent=indent, sort_keys=False) + json.dump(schema, output_file, indent=indent, sort_keys=False) + + +def find_type_node(datatype_tree: VSSNode | None, fqn: str) -> VSSNode | None: + if not datatype_tree: + return None + return datatype_tree.get_node_with_fqn(fqn) + + +def add_node( + schema: dict[str, Any], + node: VSSNode, + dtree: VSSNode | None, + no_additional_props: bool, + require_all_properties: bool, +) -> None: + schema["type"] = "object" + if isinstance(node.data, VSSDataDatatype): + ref = schema + if is_array(node.data.datatype): + schema["type"] = "array" + schema["items"] = {} + ref = schema["items"] + datatype = node.data.datatype.rstrip("[]") + if datatype in type_map: + target_type = type_map[datatype] + target_type = type_map[datatype] + ref["type"] = target_type[0] + if len(target_type) > 1: + ref["minimum"] = target_type[1] + ref["maximum"] = target_type[2] + if node.data.min is not None: + ref["minimum"] = node.data.min + if node.data.max is not None: + ref["maximum"] = node.data.max + if node.data.allowed: + ref["enum"] = node.data.allowed + else: + fqn = resolve_datatype(node.data.datatype, node.get_fqn()).rstrip("[]") + type_node = find_type_node(dtree, fqn) + if not type_node: + raise VSSJSONSchemaExporterException() + add_node(ref, type_node, dtree, no_additional_props, require_all_properties) + else: + schema["properties"] = {} + if no_additional_props: + schema["additionalProperties"] = False + for child in node.children: + if require_all_properties: + if "required" in schema: + schema["required"].append(child.name) + else: + schema["required"] = [child.name] + schema["properties"][child.name] = {} + add_node(schema["properties"][child.name], child, dtree, no_additional_props, require_all_properties)