Skip to content

Commit

Permalink
status quo
Browse files Browse the repository at this point in the history
  • Loading branch information
sschleemilch committed Nov 24, 2024
1 parent eae6f36 commit a058c36
Showing 1 changed file with 77 additions and 136 deletions.
213 changes: 77 additions & 136 deletions src/vss_tools/exporters/jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)

0 comments on commit a058c36

Please sign in to comment.