From fb1b0c3bb31550b38130aff23a91b4bbe6f92c3b Mon Sep 17 00:00:00 2001 From: Adnan Bekan Date: Tue, 25 Jul 2023 09:30:39 +0200 Subject: [PATCH 01/10] Add jsonschema exporter for VSS. Signed-off-by: Adnan Bekan --- vspec/vssexporters/vss2jsonschema.py | 136 +++++++++++++++++++++++++++ vspec2jsonschema.py | 11 +++ vspec2x.py | 5 +- 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 vspec/vssexporters/vss2jsonschema.py create mode 100755 vspec2jsonschema.py diff --git a/vspec/vssexporters/vss2jsonschema.py b/vspec/vssexporters/vss2jsonschema.py new file mode 100644 index 00000000..7c0853e5 --- /dev/null +++ b/vspec/vssexporters/vss2jsonschema.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 + +# (c) 2023 BMW Group +# +# All files and artifacts in this repository are licensed under the +# provisions of the license provided by the LICENSE file in this repository. +# +# +# Convert vspec tree to JSON Schema + +from vspec.model.vsstree import VSSNode +import argparse +import json +import logging +from typing import Dict, Any +from vspec.loggingconfig import initLogging + +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" +} + +def add_arguments(parser: argparse.ArgumentParser): + parser.description = "The JSON schema exporter does not support any additional arguments." + + + +def export_node(json_dict, node, config, print_uuid): + #TODO adding json schema version might be great + #TODO check if needed, formating of jsonschema is also possible + # tags starting with $ sign are left for custom extensions and they are not part of official JSON Schema + json_dict[node.name] = { + "$VSStype": str(node.type.value), + "description": node.description, + } + + if node.is_signal() or node.is_property(): + json_dict[node.name]["$datatype"] = node.data_type_str + json_dict[node.name]["type"] = type_map[node.data_type_str] + + #TODO map types, unless we want to keep original + + # many optional attributes are initialized to "" in vsstree.py + if node.min != "": + json_dict[node.name]["minimum"] = node.min + if node.max != "": + json_dict[node.name]["maximum"] = node.max + if node.allowed != "": + json_dict[node.name]["enum"] = node.allowed + if node.default != "": + json_dict[node.name]["default"] = node.default + if node.deprecation != "": + json_dict[node.name]["$deprecation"] = node.deprecation + if node.is_struct(): + #change type to object + json_dict[node.type]["type"] = "object" + + # in case of unit or aggregate, the attribute will be missing + try: + json_dict[node.name]["unit"] = str(node.unit.value) + except AttributeError: + pass + try: + json_dict[node.name]["$aggregate"] = node.aggregate + if node.aggregate == True: + #change type to object + json_dict[node.type]["type"] = "object" + except AttributeError: + pass + + if node.comment != "": + json_dict[node.name]["comment"] = node.comment + + if print_uuid: + json_dict[node.name]["$uuid"] = node.uuid + + for k, v in node.extended_attributes.items(): + json_dict[node.name][k] = v + + # Generate child nodes + if node.is_branch() or node.is_struct(): + #todo if struct, type could be linked to object and then list elements as properties + json_dict[node.name]["$children"] = {} + for child in node.children: + export_node(json_dict[node.name]["$children"], child, config, print_uuid) + + +def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid, data_type_root: VSSNode): + logging.info("Generating JSON Schema...") + indent = None + + signals_json_schema: Dict[str, Any] = {} + export_node(signals_json_schema, signal_root, config, print_uuid) + + # Add data types to the schema + if data_type_root is not None: + data_types_json_schema: Dict[str, Any] = {} + export_node(data_types_json_schema, data_type_root, config, print_uuid) + signals_json_schema["$ComplexDataTypes"] = data_types_json_schema + + with open(config.output_file, 'w') as f: + json.dump(signals_json_schema, f, indent=indent, sort_keys=True) + + +if __name__ == "__main__": + initLogging() + + parser = argparse.ArgumentParser() + add_arguments(parser) + args = parser.parse_args() + + # Assuming you have the necessary variables signal_root and data_type_root to represent the vspec model. + # Call the generate_json_schema function with appropriate arguments to generate the JSON schema. + generate_json_schema(args, signal_root, print_uuid=False, data_type_root=data_type_root) diff --git a/vspec2jsonschema.py b/vspec2jsonschema.py new file mode 100755 index 00000000..864161e2 --- /dev/null +++ b/vspec2jsonschema.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# +# +# Convert vspec2jsonschema wrapper for vspec2x +# + +import sys +import vspec2x + +if __name__ == "__main__": + vspec2x.main(["--format", "jsonschema"]+sys.argv[1:]) diff --git a/vspec2x.py b/vspec2x.py index 1619fe6a..4dfced0c 100755 --- a/vspec2x.py +++ b/vspec2x.py @@ -22,9 +22,9 @@ from vspec.vssexporters import vss2json, vss2csv, vss2yaml, \ - vss2binary, vss2franca, vss2ddsidl, vss2graphql, vss2protobuf + vss2binary, vss2franca, vss2ddsidl, vss2graphql, vss2protobuf, vss2jsonschema -SUPPORTED_STRUCT_EXPORT_FORMATS = set(["json", "yaml", "csv", "protobuf"]) +SUPPORTED_STRUCT_EXPORT_FORMATS = set(["json", "yaml", "csv", "protobuf", "jsonschema"]) class Exporter(Enum): @@ -43,6 +43,7 @@ def export(config: argparse.Namespace, root: VSSNode): idl = vss2ddsidl graphql = vss2graphql protobuf = vss2protobuf + jsonschema = vss2jsonschema def __str__(self): return self.name From d843fdbefbbe3298b1c373f1559e228f93d35dc1 Mon Sep 17 00:00:00 2001 From: Sebastian Schildt Date: Mon, 18 Sep 2023 10:23:55 +0200 Subject: [PATCH 02/10] Update header and fix linter errors Signed-off-by: Sebastian Schildt Signed-off-by: Adnan Bekan --- vspec/vssexporters/vss2jsonschema.py | 41 +++++------ vssexporters/vss2openapi.py | 105 +++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 vssexporters/vss2openapi.py diff --git a/vspec/vssexporters/vss2jsonschema.py b/vspec/vssexporters/vss2jsonschema.py index 7c0853e5..2470354b 100644 --- a/vspec/vssexporters/vss2jsonschema.py +++ b/vspec/vssexporters/vss2jsonschema.py @@ -1,12 +1,15 @@ #!/usr/bin/env python3 - -# (c) 2023 BMW Group # -# All files and artifacts in this repository are licensed under the -# provisions of the license provided by the LICENSE file in this repository. +# Copyright (c) 2023 Contributors to COVESA +# +# This program and the accompanying materials are made available under the +# terms of the Mozilla Public License 2.0 which is available at +# https://www.mozilla.org/en-US/MPL/2.0/ # +# SPDX-License-Identifier: MPL-2.0 # -# Convert vspec tree to JSON Schema +# Convert vspec tree to OpenAPI compatible JSON schema + from vspec.model.vsstree import VSSNode import argparse @@ -42,14 +45,14 @@ "string[]": "array" } + def add_arguments(parser: argparse.ArgumentParser): parser.description = "The JSON schema exporter does not support any additional arguments." - def export_node(json_dict, node, config, print_uuid): - #TODO adding json schema version might be great - #TODO check if needed, formating of jsonschema is also possible + # TODO adding json schema version might be great + # TODO check if needed, formating of jsonschema is also possible # tags starting with $ sign are left for custom extensions and they are not part of official JSON Schema json_dict[node.name] = { "$VSStype": str(node.type.value), @@ -60,7 +63,7 @@ def export_node(json_dict, node, config, print_uuid): json_dict[node.name]["$datatype"] = node.data_type_str json_dict[node.name]["type"] = type_map[node.data_type_str] - #TODO map types, unless we want to keep original + # TODO map types, unless we want to keep original # many optional attributes are initialized to "" in vsstree.py if node.min != "": @@ -73,8 +76,8 @@ def export_node(json_dict, node, config, print_uuid): json_dict[node.name]["default"] = node.default if node.deprecation != "": json_dict[node.name]["$deprecation"] = node.deprecation - if node.is_struct(): - #change type to object + if node.is_struct(): + # change type to object json_dict[node.type]["type"] = "object" # in case of unit or aggregate, the attribute will be missing @@ -84,9 +87,9 @@ def export_node(json_dict, node, config, print_uuid): pass try: json_dict[node.name]["$aggregate"] = node.aggregate - if node.aggregate == True: - #change type to object - json_dict[node.type]["type"] = "object" + if node.aggregate is True: + # change type to object + json_dict[node.type]["type"] = "object" except AttributeError: pass @@ -101,7 +104,7 @@ def export_node(json_dict, node, config, print_uuid): # Generate child nodes if node.is_branch() or node.is_struct(): - #todo if struct, type could be linked to object and then list elements as properties + # todo if struct, type could be linked to object and then list elements as properties json_dict[node.name]["$children"] = {} for child in node.children: export_node(json_dict[node.name]["$children"], child, config, print_uuid) @@ -126,11 +129,3 @@ def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid, data_ty if __name__ == "__main__": initLogging() - - parser = argparse.ArgumentParser() - add_arguments(parser) - args = parser.parse_args() - - # Assuming you have the necessary variables signal_root and data_type_root to represent the vspec model. - # Call the generate_json_schema function with appropriate arguments to generate the JSON schema. - generate_json_schema(args, signal_root, print_uuid=False, data_type_root=data_type_root) diff --git a/vssexporters/vss2openapi.py b/vssexporters/vss2openapi.py new file mode 100644 index 00000000..7eed65c9 --- /dev/null +++ b/vssexporters/vss2openapi.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023 Contributors to COVESA +# +# This program and the accompanying materials are made available under the +# terms of the Mozilla Public License 2.0 which is available at +# https://www.mozilla.org/en-US/MPL/2.0/ +# +# SPDX-License-Identifier: MPL-2.0 +# +# Convert vspec tree to OpenAPI compatible JSON schema + +import argparse +import json +from vspec.model.vsstree import VSSNode, VSSType + + +def add_arguments(parser: argparse.ArgumentParser): + parser.add_argument('--openapi-all-extended-attributes', action='store_true', + help="Generate all extended attributes found in the model \ + (default is generating only those given by the -e/--extended-attributes parameter).") + + +def vss_to_openapi_datatype(vssdatatype: str): + if "int" in vssdatatype and not vssdatatype.endswith("[]"): + return "integer" + elif "int" in vssdatatype and vssdatatype.endswith("[]"): + return "integer[]" + elif vssdatatype == "float" or vssdatatype == "double": + return "number" + elif vssdatatype == "boolean": + return "boolean" + elif vssdatatype == "string": + return "string" + else: + print(f"OpenAPI warning: Do not know who to convert {vssdatatype}, passing through.") + return vssdatatype + + +def export_node(json_dict, node, config, print_uuid): + + json_dict[node.name] = {} + + if node.type == VSSType.SENSOR or node.type == VSSType.ACTUATOR or node.type == VSSType.ATTRIBUTE: + json_dict[node.name]["type"] = vss_to_openapi_datatype(str(node.datatype.value)) + + json_dict[node.name]["vss_type"] = str(node.type.value) + + # many optional attributes are initilized to "" in vsstree.py + if node.min != "": + json_dict[node.name]["min"] = node.min + if node.max != "": + json_dict[node.name]["max"] = node.max + if node.allowed != "": + json_dict[node.name]["allowed"] = node.allowed + if node.default != "": + json_dict[node.name]["default"] = node.default + if node.deprecation != "": + json_dict[node.name]["deprecation"] = node.deprecation + + # in case of unit or aggregate, the attribute will be missing + try: + json_dict[node.name]["unit"] = str(node.unit.value) + except AttributeError: + pass + try: + json_dict[node.name]["aggregate"] = node.aggregate + except AttributeError: + pass + + json_dict[node.name]["description"] = node.description + if node.comment != "": + json_dict[node.name]["comment"] = node.comment + + if print_uuid: + json_dict[node.name]["uuid"] = node.uuid + + for k, v in node.extended_attributes.items(): + if not config.json_all_extended_attributes and k not in VSSNode.whitelisted_extended_attributes: + continue + json_dict[node.name][k] = v + + # Might be better to not generate child dict, if there are no children + # if node.type == VSSType.BRANCH and len(node.children) != 0: + # json_dict[node.name]["children"]={} + + # But old JSON code always generates children, so lets do so to + if node.type == VSSType.BRANCH: + json_dict[node.name]["properties"] = {} + + for child in node.children: + export_node(json_dict[node.name]["properties"], child, config, print_uuid) + + +def export(config: argparse.Namespace, root: VSSNode, print_uuid): + print("Generating OpenAPI output...") + json_dict = {} + export_node(json_dict, root, config, print_uuid) + outfile = open(config.output_file, 'w') + if config.json_pretty: + print("Serializing pretty JSON...") + json.dump(json_dict, outfile, indent=2, sort_keys=True) + else: + print("Serializing compact JSON...") + json.dump(json_dict, outfile, indent=None, sort_keys=True) From b9bda1fba326ea6a29c29643362c1ef856a91543 Mon Sep 17 00:00:00 2001 From: Sebastian Schildt Date: Mon, 18 Sep 2023 14:43:51 +0200 Subject: [PATCH 03/10] Support pretty printing of schwema Signed-off-by: Sebastian Schildt Signed-off-by: Adnan Bekan --- vspec/vssexporters/vss2jsonschema.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vspec/vssexporters/vss2jsonschema.py b/vspec/vssexporters/vss2jsonschema.py index 2470354b..dc6d2deb 100644 --- a/vspec/vssexporters/vss2jsonschema.py +++ b/vspec/vssexporters/vss2jsonschema.py @@ -111,8 +111,11 @@ def export_node(json_dict, node, config, print_uuid): def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid, data_type_root: VSSNode): - logging.info("Generating JSON Schema...") + logging.info("Generating JSON chema...") indent = None + if config.json_pretty: + logging.info("Serializing pretty JSON schema...") + indent = 2 signals_json_schema: Dict[str, Any] = {} export_node(signals_json_schema, signal_root, config, print_uuid) @@ -124,6 +127,7 @@ def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid, data_ty signals_json_schema["$ComplexDataTypes"] = data_types_json_schema with open(config.output_file, 'w') as f: + json.dump(signals_json_schema, f, indent=indent, sort_keys=True) From c3eb419728889464ea58c04e4ceff9d77574d35c Mon Sep 17 00:00:00 2001 From: Adnan Bekan Date: Wed, 20 Sep 2023 14:39:45 +0200 Subject: [PATCH 04/10] cleanup Signed-off-by: Adnan Bekan --- vssexporters/vss2openapi.py | 105 ------------------------------------ 1 file changed, 105 deletions(-) delete mode 100644 vssexporters/vss2openapi.py diff --git a/vssexporters/vss2openapi.py b/vssexporters/vss2openapi.py deleted file mode 100644 index 7eed65c9..00000000 --- a/vssexporters/vss2openapi.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2023 Contributors to COVESA -# -# This program and the accompanying materials are made available under the -# terms of the Mozilla Public License 2.0 which is available at -# https://www.mozilla.org/en-US/MPL/2.0/ -# -# SPDX-License-Identifier: MPL-2.0 -# -# Convert vspec tree to OpenAPI compatible JSON schema - -import argparse -import json -from vspec.model.vsstree import VSSNode, VSSType - - -def add_arguments(parser: argparse.ArgumentParser): - parser.add_argument('--openapi-all-extended-attributes', action='store_true', - help="Generate all extended attributes found in the model \ - (default is generating only those given by the -e/--extended-attributes parameter).") - - -def vss_to_openapi_datatype(vssdatatype: str): - if "int" in vssdatatype and not vssdatatype.endswith("[]"): - return "integer" - elif "int" in vssdatatype and vssdatatype.endswith("[]"): - return "integer[]" - elif vssdatatype == "float" or vssdatatype == "double": - return "number" - elif vssdatatype == "boolean": - return "boolean" - elif vssdatatype == "string": - return "string" - else: - print(f"OpenAPI warning: Do not know who to convert {vssdatatype}, passing through.") - return vssdatatype - - -def export_node(json_dict, node, config, print_uuid): - - json_dict[node.name] = {} - - if node.type == VSSType.SENSOR or node.type == VSSType.ACTUATOR or node.type == VSSType.ATTRIBUTE: - json_dict[node.name]["type"] = vss_to_openapi_datatype(str(node.datatype.value)) - - json_dict[node.name]["vss_type"] = str(node.type.value) - - # many optional attributes are initilized to "" in vsstree.py - if node.min != "": - json_dict[node.name]["min"] = node.min - if node.max != "": - json_dict[node.name]["max"] = node.max - if node.allowed != "": - json_dict[node.name]["allowed"] = node.allowed - if node.default != "": - json_dict[node.name]["default"] = node.default - if node.deprecation != "": - json_dict[node.name]["deprecation"] = node.deprecation - - # in case of unit or aggregate, the attribute will be missing - try: - json_dict[node.name]["unit"] = str(node.unit.value) - except AttributeError: - pass - try: - json_dict[node.name]["aggregate"] = node.aggregate - except AttributeError: - pass - - json_dict[node.name]["description"] = node.description - if node.comment != "": - json_dict[node.name]["comment"] = node.comment - - if print_uuid: - json_dict[node.name]["uuid"] = node.uuid - - for k, v in node.extended_attributes.items(): - if not config.json_all_extended_attributes and k not in VSSNode.whitelisted_extended_attributes: - continue - json_dict[node.name][k] = v - - # Might be better to not generate child dict, if there are no children - # if node.type == VSSType.BRANCH and len(node.children) != 0: - # json_dict[node.name]["children"]={} - - # But old JSON code always generates children, so lets do so to - if node.type == VSSType.BRANCH: - json_dict[node.name]["properties"] = {} - - for child in node.children: - export_node(json_dict[node.name]["properties"], child, config, print_uuid) - - -def export(config: argparse.Namespace, root: VSSNode, print_uuid): - print("Generating OpenAPI output...") - json_dict = {} - export_node(json_dict, root, config, print_uuid) - outfile = open(config.output_file, 'w') - if config.json_pretty: - print("Serializing pretty JSON...") - json.dump(json_dict, outfile, indent=2, sort_keys=True) - else: - print("Serializing compact JSON...") - json.dump(json_dict, outfile, indent=None, sort_keys=True) From 3b4ccfe1d7ab63d5eb583c333b08b156e02c8c32 Mon Sep 17 00:00:00 2001 From: Adnan Bekan Date: Wed, 20 Sep 2023 21:13:46 +0200 Subject: [PATCH 05/10] Update tool with additional arguments and fix issue of JSON Schema validation Signed-off-by: Adnan Bekan --- vspec/vssexporters/vss2jsonschema.py | 80 ++++++++++++++++------------ 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/vspec/vssexporters/vss2jsonschema.py b/vspec/vssexporters/vss2jsonschema.py index dc6d2deb..e6d4125c 100644 --- a/vspec/vssexporters/vss2jsonschema.py +++ b/vspec/vssexporters/vss2jsonschema.py @@ -8,7 +8,7 @@ # # SPDX-License-Identifier: MPL-2.0 # -# Convert vspec tree to OpenAPI compatible JSON schema +# Convert vspec tree compatible JSON schema from vspec.model.vsstree import VSSNode @@ -47,20 +47,21 @@ def add_arguments(parser: argparse.ArgumentParser): - parser.description = "The JSON schema exporter does not support any additional arguments." - + parser.add_argument('--jsonschema-all-extended-attributes', action='store_true', + help="Generate all extended attributes found in the model. Should not be used with strict mode JSON Schema validators." + "(default is generating only those given by the -e/--extended-attributes parameter).") + parser.add_argument('--jsonschema-pretty', action='store_true', + help=" Pretty print JSON Schema output.") def export_node(json_dict, node, config, print_uuid): # TODO adding json schema version might be great # TODO check if needed, formating of jsonschema is also possible # tags starting with $ sign are left for custom extensions and they are not part of official JSON Schema json_dict[node.name] = { - "$VSStype": str(node.type.value), "description": node.description, } if node.is_signal() or node.is_property(): - json_dict[node.name]["$datatype"] = node.data_type_str json_dict[node.name]["type"] = type_map[node.data_type_str] # TODO map types, unless we want to keep original @@ -74,30 +75,36 @@ def export_node(json_dict, node, config, print_uuid): json_dict[node.name]["enum"] = node.allowed if node.default != "": json_dict[node.name]["default"] = node.default - if node.deprecation != "": - json_dict[node.name]["$deprecation"] = node.deprecation if node.is_struct(): # change type to object json_dict[node.type]["type"] = "object" - # in case of unit or aggregate, the attribute will be missing - try: - json_dict[node.name]["unit"] = str(node.unit.value) - except AttributeError: - pass - try: - json_dict[node.name]["$aggregate"] = node.aggregate - if node.aggregate is True: - # change type to object - json_dict[node.type]["type"] = "object" - except AttributeError: - pass - - if node.comment != "": - json_dict[node.name]["comment"] = node.comment - - if print_uuid: - json_dict[node.name]["$uuid"] = node.uuid + if config.jsonschema_all_extended_attributes: + if node.type !="": + json_dict[node.name]["x-VSStype"] = str(node.type.value) + if node.data_type_str !="": + json_dict[node.name]["x-datatype"] = node.data_type_str + if node.deprecation != "": + json_dict[node.name]["x-deprecation"] = node.deprecation + + # in case of unit or aggregate, the attribute will be missing + try: + json_dict[node.name]["x-unit"] = str(node.unit.value) + except AttributeError: + pass + try: + json_dict[node.name]["x-aggregate"] = node.aggregate + if node.aggregate == True: + #change type to object + json_dict[node.type]["type"] = "object" + except AttributeError: + pass + + if node.comment != "": + json_dict[node.name]["x-comment"] = node.comment + + if print_uuid: + json_dict[node.name]["x-uuid"] = node.uuid for k, v in node.extended_attributes.items(): json_dict[node.name][k] = v @@ -105,15 +112,15 @@ def export_node(json_dict, node, config, print_uuid): # Generate child nodes if node.is_branch() or node.is_struct(): # todo if struct, type could be linked to object and then list elements as properties - json_dict[node.name]["$children"] = {} + json_dict[node.name]["properties"] = {} for child in node.children: - export_node(json_dict[node.name]["$children"], child, config, print_uuid) + export_node(json_dict[node.name]["properties"], child, config, print_uuid) def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid, data_type_root: VSSNode): - logging.info("Generating JSON chema...") + logging.info("Generating JSON schema...") indent = None - if config.json_pretty: + if config.jsonschema_pretty: logging.info("Serializing pretty JSON schema...") indent = 2 @@ -124,11 +131,18 @@ def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid, data_ty if data_type_root is not None: data_types_json_schema: Dict[str, Any] = {} export_node(data_types_json_schema, data_type_root, config, print_uuid) - signals_json_schema["$ComplexDataTypes"] = data_types_json_schema - + if config.jsonschema_all_extended_attributes: + signals_json_schema["x-ComplexDataTypes"] = data_types_json_schema + + top_node = signals_json_schema.pop("Vehicle") + # Create a new JSON Schema object + json_schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Vehicle", + "type": "object", + **top_node} with open(config.output_file, 'w') as f: - - json.dump(signals_json_schema, f, indent=indent, sort_keys=True) + json.dump(json_schema, f, indent=indent, sort_keys=False) if __name__ == "__main__": From e04bb14bed4d3870ec071a1c2d0d8c7f8c0f8e93 Mon Sep 17 00:00:00 2001 From: Adnan Bekan Date: Wed, 20 Sep 2023 21:52:06 +0200 Subject: [PATCH 06/10] Update Formatting and fix few linting issues Signed-off-by: Adnan Bekan --- vspec/vssexporters/vss2jsonschema.py | 46 +++++++++++++--------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/vspec/vssexporters/vss2jsonschema.py b/vspec/vssexporters/vss2jsonschema.py index e6d4125c..26716518 100644 --- a/vspec/vssexporters/vss2jsonschema.py +++ b/vspec/vssexporters/vss2jsonschema.py @@ -10,12 +10,11 @@ # # Convert vspec tree compatible JSON schema - -from vspec.model.vsstree import VSSNode import argparse import json import logging from typing import Dict, Any +from vspec.model.vsstree import VSSNode from vspec.loggingconfig import initLogging type_map = { @@ -47,16 +46,17 @@ def add_arguments(parser: argparse.ArgumentParser): + """Check for input arguments.""" parser.add_argument('--jsonschema-all-extended-attributes', action='store_true', - help="Generate all extended attributes found in the model. Should not be used with strict mode JSON Schema validators." - "(default is generating only those given by the -e/--extended-attributes parameter).") + help="Generate all extended attributes found in the model." + "Should not be used with strict mode JSON Schema validators.") parser.add_argument('--jsonschema-pretty', action='store_true', help=" Pretty print JSON Schema output.") + def export_node(json_dict, node, config, print_uuid): - # TODO adding json schema version might be great - # TODO check if needed, formating of jsonschema is also possible - # tags starting with $ sign are left for custom extensions and they are not part of official JSON Schema + """Preparing nodes for JSON schema output.""" + # keyword with X- sign are left for extensions and they are not part of official JSON schema json_dict[node.name] = { "description": node.description, } @@ -64,8 +64,6 @@ def export_node(json_dict, node, config, print_uuid): if node.is_signal() or node.is_property(): json_dict[node.name]["type"] = type_map[node.data_type_str] - # TODO map types, unless we want to keep original - # many optional attributes are initialized to "" in vsstree.py if node.min != "": json_dict[node.name]["minimum"] = node.min @@ -80,13 +78,13 @@ def export_node(json_dict, node, config, print_uuid): json_dict[node.type]["type"] = "object" if config.jsonschema_all_extended_attributes: - if node.type !="": - json_dict[node.name]["x-VSStype"] = str(node.type.value) - if node.data_type_str !="": + if node.type != "": + json_dict[node.name]["x-VSStype"] = str(node.type.value) + if node.data_type_str != "": json_dict[node.name]["x-datatype"] = node.data_type_str if node.deprecation != "": json_dict[node.name]["x-deprecation"] = node.deprecation - + # in case of unit or aggregate, the attribute will be missing try: json_dict[node.name]["x-unit"] = str(node.unit.value) @@ -94,9 +92,9 @@ def export_node(json_dict, node, config, print_uuid): pass try: json_dict[node.name]["x-aggregate"] = node.aggregate - if node.aggregate == True: - #change type to object - json_dict[node.type]["type"] = "object" + if node.aggregate: + # change type to object + json_dict[node.type]["type"] = "object" except AttributeError: pass @@ -111,13 +109,13 @@ def export_node(json_dict, node, config, print_uuid): # Generate child nodes if node.is_branch() or node.is_struct(): - # todo if struct, type could be linked to object and then list elements as properties json_dict[node.name]["properties"] = {} for child in node.children: export_node(json_dict[node.name]["properties"], child, config, print_uuid) def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid, data_type_root: VSSNode): + """Export function for generating JSON schema file.""" logging.info("Generating JSON schema...") indent = None if config.jsonschema_pretty: @@ -133,16 +131,16 @@ def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid, data_ty export_node(data_types_json_schema, data_type_root, config, print_uuid) if config.jsonschema_all_extended_attributes: signals_json_schema["x-ComplexDataTypes"] = data_types_json_schema - + top_node = signals_json_schema.pop("Vehicle") # Create a new JSON Schema object json_schema = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Vehicle", - "type": "object", - **top_node} - with open(config.output_file, 'w') as f: - json.dump(json_schema, f, indent=indent, sort_keys=False) + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Vehicle", + "type": "object", + **top_node} + with open(config.output_file, 'w', encoding="utf-8") as output_file: + json.dump(json_schema, output_file, indent=indent, sort_keys=False) if __name__ == "__main__": From d01541555ad45bc8cb4f71e95540618bcc3f8382 Mon Sep 17 00:00:00 2001 From: Sebastian Schildt Date: Fri, 22 Sep 2023 17:51:33 +0200 Subject: [PATCH 07/10] Allow using models with different root than 'Vehicle' Signed-off-by: Sebastian Schildt --- vspec/vssexporters/vss2jsonschema.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/vspec/vssexporters/vss2jsonschema.py b/vspec/vssexporters/vss2jsonschema.py index 26716518..4ebf61bf 100644 --- a/vspec/vssexporters/vss2jsonschema.py +++ b/vspec/vssexporters/vss2jsonschema.py @@ -132,13 +132,17 @@ def export(config: argparse.Namespace, signal_root: VSSNode, print_uuid, data_ty if config.jsonschema_all_extended_attributes: signals_json_schema["x-ComplexDataTypes"] = data_types_json_schema - top_node = signals_json_schema.pop("Vehicle") - # Create a new JSON Schema object + # 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": "Vehicle", + "title": top_node_name, "type": "object", - **top_node} + **signals_json_schema} with open(config.output_file, 'w', encoding="utf-8") as output_file: json.dump(json_schema, output_file, indent=indent, sort_keys=False) From 69e96d73b4e8c27fe85b48e95d9b30427e508caa Mon Sep 17 00:00:00 2001 From: Sebastian Schildt Date: Fri, 22 Sep 2023 18:17:49 +0200 Subject: [PATCH 08/10] Optionally allow stricter schema Signed-off-by: Sebastian Schildt --- vspec/vssexporters/vss2jsonschema.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vspec/vssexporters/vss2jsonschema.py b/vspec/vssexporters/vss2jsonschema.py index 4ebf61bf..75c85af1 100644 --- a/vspec/vssexporters/vss2jsonschema.py +++ b/vspec/vssexporters/vss2jsonschema.py @@ -50,6 +50,10 @@ def add_arguments(parser: argparse.ArgumentParser): parser.add_argument('--jsonschema-all-extended-attributes', action='store_true', help="Generate all extended attributes found in the model." "Should not be used with strict mode JSON Schema validators.") + parser.add_argument("--jsonschema-disallow-additional-properties", action='store_true', + help="Do not allow properties not defined in VSS tree") + parser.add_argument("--jsonschema-require-all-properties", action='store_true', + help="Require all elements defined in VSS tree for a valid object") parser.add_argument('--jsonschema-pretty', action='store_true', help=" Pretty print JSON Schema output.") @@ -109,7 +113,11 @@ def export_node(json_dict, node, config, print_uuid): # Generate child nodes if node.is_branch() or node.is_struct(): + if config.jsonschema_disallow_additional_properties: + json_dict[node.name]["additionalProperties"] = False json_dict[node.name]["properties"] = {} + if config.jsonschema_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, config, print_uuid) From 4c6fd86062156c143c2b24694af2581b72efc88b Mon Sep 17 00:00:00 2001 From: Sebastian Schildt Date: Fri, 22 Sep 2023 18:48:40 +0200 Subject: [PATCH 09/10] Add basic tests to JSON schema exporter Signed-off-by: Sebastian Schildt --- .pre-commit-config.yaml | 2 +- tests/vspec/test_comment/expected.jsonschema | 1 + tests/vspec/test_datatypes/expected.jsonschema | 1 + tests/vspec/test_generic.py | 3 ++- tests/vspec/test_instances/expected.jsonschema | 1 + 5 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 tests/vspec/test_comment/expected.jsonschema create mode 100644 tests/vspec/test_datatypes/expected.jsonschema create mode 100644 tests/vspec/test_instances/expected.jsonschema diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0158bf7b..fd9be2b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: exclude_types: ["csv", "proto"] - id: end-of-file-fixer exclude_types: ["json", "proto"] - exclude: '.*.fidl' + exclude: ".*\\.[fidl|jsonschema]" - id: check-yaml - id: check-added-large-files diff --git a/tests/vspec/test_comment/expected.jsonschema b/tests/vspec/test_comment/expected.jsonschema new file mode 100644 index 00000000..80415673 --- /dev/null +++ b/tests/vspec/test_comment/expected.jsonschema @@ -0,0 +1 @@ +{"$schema": "https://json-schema.org/draft/2020-12/schema", "title": "A", "type": "object", "description": "Branch A.", "properties": {"SingleLineNotQuoted": {"description": "A sensor.", "type": "number"}, "SingleLineInternalQuotes": {"description": "A sensor.", "type": "number"}, "SingleLineQuoted": {"description": "A sensor.", "type": "number"}, "SingleLineQuotedInternalQuotes": {"description": "A sensor.", "type": "number"}, "SingleLineComma": {"description": "A sensor.", "type": "number"}, "SingleLineCommaQuoted": {"description": "A sensor.", "type": "number"}, "MultiLineCommaNotQuoted": {"description": "A sensor.", "type": "number"}, "MultiLineCommaQuoted": {"description": "A sensor.", "type": "number"}, "MultiLineStyleInitialBreak": {"description": "A sensor.", "type": "number"}, "MultiLineLiteralStyleQuote": {"description": "A sensor.", "type": "number"}}} \ No newline at end of file diff --git a/tests/vspec/test_datatypes/expected.jsonschema b/tests/vspec/test_datatypes/expected.jsonschema new file mode 100644 index 00000000..05b80d74 --- /dev/null +++ b/tests/vspec/test_datatypes/expected.jsonschema @@ -0,0 +1 @@ +{"$schema": "https://json-schema.org/draft/2020-12/schema", "title": "A", "type": "object", "description": "Branch A.", "properties": {"UInt8": {"description": "A uint8.", "type": "integer"}, "Int8": {"description": "An int8.", "type": "integer"}, "UInt16": {"description": "A uint16.", "type": "integer"}, "Int16": {"description": "An int16.", "type": "integer"}, "UInt32": {"description": "A uint32.", "type": "integer"}, "Int32": {"description": "An int32", "type": "integer"}, "UInt64": {"description": "A uint64.", "type": "integer"}, "Int64": {"description": "An int64", "type": "integer"}, "IsBoolean": {"description": "A boolean", "type": "boolean"}, "Float": {"description": "A float.", "type": "number"}, "Double": {"description": "A double.", "type": "number"}, "String": {"description": "A string.", "type": "string"}}} \ No newline at end of file diff --git a/tests/vspec/test_generic.py b/tests/vspec/test_generic.py index 1eeed2c8..9ecbffc9 100644 --- a/tests/vspec/test_generic.py +++ b/tests/vspec/test_generic.py @@ -58,6 +58,7 @@ def run_exporter(directory, exporter): def test_exporters(directory, change_test_dir): # Run all "supported" exporters, i.e. not those in contrib # Exception is "binary", as it is assumed output may vary depending on target - exporters = ["json", "ddsidl", "csv", "yaml", "franca", "graphql"] + exporters = ["json", "jsonschema", "ddsidl", "csv", "yaml", "franca", "graphql"] + for exporter in exporters: run_exporter(directory, exporter) diff --git a/tests/vspec/test_instances/expected.jsonschema b/tests/vspec/test_instances/expected.jsonschema new file mode 100644 index 00000000..1792aec9 --- /dev/null +++ b/tests/vspec/test_instances/expected.jsonschema @@ -0,0 +1 @@ +{"$schema": "https://json-schema.org/draft/2020-12/schema", "title": "A", "type": "object", "description": "Branch A.", "properties": {"B": {"description": "This description will be duplicated.", "properties": {"Row1": {"description": "This description will be duplicated.", "properties": {"Left": {"description": "This description will be duplicated.", "properties": {"C": {"description": "This description will also exist multiple times.", "type": "integer"}}}, "Right": {"description": "This description will be duplicated.", "properties": {"C": {"description": "This description will also exist multiple times.", "type": "integer"}}}}}, "Row2": {"description": "This description will be duplicated.", "properties": {"Left": {"description": "This description will be duplicated.", "properties": {"C": {"description": "This description will also exist multiple times.", "type": "integer"}}}, "Right": {"description": "This description will be duplicated.", "properties": {"C": {"description": "This description will also exist multiple times.", "type": "integer"}}}}}}}}} \ No newline at end of file From 7aac780dff94eba89b870263a5e9f090807b599b Mon Sep 17 00:00:00 2001 From: Sebastian Schildt Date: Tue, 17 Oct 2023 13:12:40 +0200 Subject: [PATCH 10/10] Update documentation for jsonschema Signed-off-by: Sebastian Schildt --- docs/vspec2x.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/vspec2x.md b/docs/vspec2x.md index 0f888696..28733d5c 100644 --- a/docs/vspec2x.md +++ b/docs/vspec2x.md @@ -300,6 +300,36 @@ Lets the exporter generate _all_ extended metadata attributes found in the model ### --json-pretty If the paramter is set it will pretty-print the JSON output, otherwise you will get a minimized version +## JSONSCHEMA exporter notes + +### --jsonschema-all-extended-attributes +Lets the exporter generate _all_ extended metadata attributes found in the model. By default the exporter is generating only those given by the `-e`/`--extended-attributes` parameter. This will also add unconverted VSS standard attribtues into the schema using the following attributes + +| VSS attribute | in schema | +|---------------|---------------| +| type | x-VSStype | +| datatype | x-datatype | +| deprecation | x-deprecation | +| aggregate | x-aggregate | +| comment | x-comment | +| uuid | x-uuid | + +Not that strict JSON schema validators might not accept jsonschemas whoch such extra, non-standard entries. + +### --jsonschema-disallow-additional-properties +Do not allow properties not defined in VSS tree, when elements are validated agains the schema, what this basically does is setting + +```json +"additionalProperties": false +``` +for all defined objects. See: https://json-schema.org/draft/2020-12/json-schema-core#additionalProperties + +### --jsonschema-require-all-properties +Require all elements defined in VSS tree for a valid object, i.e. this populates the `required` list with all childs. See: https://json-schema.org/draft/2020-12/json-schema-validation#name-required + +### --jsonschema-pretty +If the paramter is set it will pretty-print the JSON output, otherwise you will get a minimized version + ## YAML exporter notes ### --yaml-all-extended-attributes