avrotize 2.21.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- avrotize/__init__.py +66 -0
- avrotize/__main__.py +6 -0
- avrotize/_version.py +34 -0
- avrotize/asn1toavro.py +160 -0
- avrotize/avrotize.py +152 -0
- avrotize/avrotocpp/CMakeLists.txt.jinja +77 -0
- avrotize/avrotocpp/build.bat.jinja +7 -0
- avrotize/avrotocpp/build.sh.jinja +7 -0
- avrotize/avrotocpp/dataclass_body.jinja +108 -0
- avrotize/avrotocpp/vcpkg.json.jinja +21 -0
- avrotize/avrotocpp.py +483 -0
- avrotize/avrotocsharp/README.md.jinja +166 -0
- avrotize/avrotocsharp/class_test.cs.jinja +266 -0
- avrotize/avrotocsharp/dataclass_core.jinja +293 -0
- avrotize/avrotocsharp/enum_test.cs.jinja +20 -0
- avrotize/avrotocsharp/project.csproj.jinja +30 -0
- avrotize/avrotocsharp/project.sln.jinja +34 -0
- avrotize/avrotocsharp/run_coverage.ps1.jinja +98 -0
- avrotize/avrotocsharp/run_coverage.sh.jinja +149 -0
- avrotize/avrotocsharp/testproject.csproj.jinja +19 -0
- avrotize/avrotocsharp.py +1180 -0
- avrotize/avrotocsv.py +121 -0
- avrotize/avrotodatapackage.py +173 -0
- avrotize/avrotodb.py +1383 -0
- avrotize/avrotogo/go_enum.jinja +12 -0
- avrotize/avrotogo/go_helpers.jinja +31 -0
- avrotize/avrotogo/go_struct.jinja +151 -0
- avrotize/avrotogo/go_test.jinja +47 -0
- avrotize/avrotogo/go_union.jinja +38 -0
- avrotize/avrotogo.py +476 -0
- avrotize/avrotographql.py +197 -0
- avrotize/avrotoiceberg.py +210 -0
- avrotize/avrotojava/class_test.java.jinja +212 -0
- avrotize/avrotojava/enum_test.java.jinja +21 -0
- avrotize/avrotojava/testproject.pom.jinja +54 -0
- avrotize/avrotojava.py +2156 -0
- avrotize/avrotojs.py +250 -0
- avrotize/avrotojsons.py +481 -0
- avrotize/avrotojstruct.py +345 -0
- avrotize/avrotokusto.py +364 -0
- avrotize/avrotomd/README.md.jinja +49 -0
- avrotize/avrotomd.py +137 -0
- avrotize/avrotools.py +168 -0
- avrotize/avrotoparquet.py +208 -0
- avrotize/avrotoproto.py +359 -0
- avrotize/avrotopython/dataclass_core.jinja +241 -0
- avrotize/avrotopython/enum_core.jinja +87 -0
- avrotize/avrotopython/pyproject_toml.jinja +18 -0
- avrotize/avrotopython/test_class.jinja +97 -0
- avrotize/avrotopython/test_enum.jinja +23 -0
- avrotize/avrotopython.py +626 -0
- avrotize/avrotorust/dataclass_enum.rs.jinja +74 -0
- avrotize/avrotorust/dataclass_struct.rs.jinja +204 -0
- avrotize/avrotorust/dataclass_union.rs.jinja +105 -0
- avrotize/avrotorust.py +435 -0
- avrotize/avrotots/class_core.ts.jinja +140 -0
- avrotize/avrotots/class_test.ts.jinja +77 -0
- avrotize/avrotots/enum_core.ts.jinja +46 -0
- avrotize/avrotots/gitignore.jinja +34 -0
- avrotize/avrotots/index.ts.jinja +0 -0
- avrotize/avrotots/package.json.jinja +23 -0
- avrotize/avrotots/tsconfig.json.jinja +21 -0
- avrotize/avrotots.py +687 -0
- avrotize/avrotoxsd.py +344 -0
- avrotize/cddltostructure.py +1841 -0
- avrotize/commands.json +3496 -0
- avrotize/common.py +834 -0
- avrotize/constants.py +87 -0
- avrotize/csvtoavro.py +132 -0
- avrotize/datapackagetoavro.py +76 -0
- avrotize/dependencies/cpp/vcpkg/vcpkg.json +19 -0
- avrotize/dependencies/cs/net90/dependencies.csproj +29 -0
- avrotize/dependencies/go/go121/go.mod +6 -0
- avrotize/dependencies/java/jdk21/pom.xml +91 -0
- avrotize/dependencies/python/py312/requirements.txt +13 -0
- avrotize/dependencies/rust/stable/Cargo.toml +17 -0
- avrotize/dependencies/typescript/node22/package.json +16 -0
- avrotize/dependency_resolver.py +348 -0
- avrotize/dependency_version.py +432 -0
- avrotize/generic/generic.avsc +57 -0
- avrotize/jsonstoavro.py +2167 -0
- avrotize/jsonstostructure.py +2864 -0
- avrotize/jstructtoavro.py +878 -0
- avrotize/kstructtoavro.py +93 -0
- avrotize/kustotoavro.py +455 -0
- avrotize/openapitostructure.py +717 -0
- avrotize/parquettoavro.py +157 -0
- avrotize/proto2parser.py +498 -0
- avrotize/proto3parser.py +403 -0
- avrotize/prototoavro.py +382 -0
- avrotize/prototypes/any.avsc +19 -0
- avrotize/prototypes/api.avsc +106 -0
- avrotize/prototypes/duration.avsc +20 -0
- avrotize/prototypes/field_mask.avsc +18 -0
- avrotize/prototypes/struct.avsc +60 -0
- avrotize/prototypes/timestamp.avsc +20 -0
- avrotize/prototypes/type.avsc +253 -0
- avrotize/prototypes/wrappers.avsc +117 -0
- avrotize/structuretocddl.py +597 -0
- avrotize/structuretocpp/CMakeLists.txt.jinja +76 -0
- avrotize/structuretocpp/build.bat.jinja +3 -0
- avrotize/structuretocpp/build.sh.jinja +3 -0
- avrotize/structuretocpp/dataclass_body.jinja +50 -0
- avrotize/structuretocpp/vcpkg.json.jinja +11 -0
- avrotize/structuretocpp.py +697 -0
- avrotize/structuretocsharp/class_test.cs.jinja +180 -0
- avrotize/structuretocsharp/dataclass_core.jinja +156 -0
- avrotize/structuretocsharp/enum_test.cs.jinja +36 -0
- avrotize/structuretocsharp/json_structure_converters.cs.jinja +399 -0
- avrotize/structuretocsharp/program.cs.jinja +49 -0
- avrotize/structuretocsharp/project.csproj.jinja +17 -0
- avrotize/structuretocsharp/project.sln.jinja +34 -0
- avrotize/structuretocsharp/testproject.csproj.jinja +18 -0
- avrotize/structuretocsharp/tuple_converter.cs.jinja +121 -0
- avrotize/structuretocsharp.py +2295 -0
- avrotize/structuretocsv.py +365 -0
- avrotize/structuretodatapackage.py +659 -0
- avrotize/structuretodb.py +1125 -0
- avrotize/structuretogo/go_enum.jinja +12 -0
- avrotize/structuretogo/go_helpers.jinja +26 -0
- avrotize/structuretogo/go_interface.jinja +18 -0
- avrotize/structuretogo/go_struct.jinja +187 -0
- avrotize/structuretogo/go_test.jinja +70 -0
- avrotize/structuretogo.py +729 -0
- avrotize/structuretographql.py +502 -0
- avrotize/structuretoiceberg.py +355 -0
- avrotize/structuretojava/choice_core.jinja +34 -0
- avrotize/structuretojava/class_core.jinja +23 -0
- avrotize/structuretojava/enum_core.jinja +18 -0
- avrotize/structuretojava/equals_hashcode.jinja +30 -0
- avrotize/structuretojava/pom.xml.jinja +26 -0
- avrotize/structuretojava/tuple_core.jinja +49 -0
- avrotize/structuretojava.py +938 -0
- avrotize/structuretojs/class_core.js.jinja +33 -0
- avrotize/structuretojs/enum_core.js.jinja +10 -0
- avrotize/structuretojs/package.json.jinja +12 -0
- avrotize/structuretojs/test_class.js.jinja +84 -0
- avrotize/structuretojs/test_enum.js.jinja +58 -0
- avrotize/structuretojs/test_runner.js.jinja +45 -0
- avrotize/structuretojs.py +657 -0
- avrotize/structuretojsons.py +498 -0
- avrotize/structuretokusto.py +639 -0
- avrotize/structuretomd/README.md.jinja +204 -0
- avrotize/structuretomd.py +322 -0
- avrotize/structuretoproto.py +764 -0
- avrotize/structuretopython/dataclass_core.jinja +363 -0
- avrotize/structuretopython/enum_core.jinja +45 -0
- avrotize/structuretopython/map_alias.jinja +21 -0
- avrotize/structuretopython/pyproject_toml.jinja +23 -0
- avrotize/structuretopython/test_class.jinja +103 -0
- avrotize/structuretopython/test_enum.jinja +34 -0
- avrotize/structuretopython.py +799 -0
- avrotize/structuretorust/dataclass_enum.rs.jinja +63 -0
- avrotize/structuretorust/dataclass_struct.rs.jinja +121 -0
- avrotize/structuretorust/dataclass_union.rs.jinja +81 -0
- avrotize/structuretorust.py +714 -0
- avrotize/structuretots/class_core.ts.jinja +78 -0
- avrotize/structuretots/enum_core.ts.jinja +6 -0
- avrotize/structuretots/gitignore.jinja +8 -0
- avrotize/structuretots/index.ts.jinja +1 -0
- avrotize/structuretots/package.json.jinja +39 -0
- avrotize/structuretots/test_class.ts.jinja +35 -0
- avrotize/structuretots/tsconfig.json.jinja +21 -0
- avrotize/structuretots.py +740 -0
- avrotize/structuretoxsd.py +679 -0
- avrotize/xsdtoavro.py +413 -0
- avrotize-2.21.1.dist-info/METADATA +1319 -0
- avrotize-2.21.1.dist-info/RECORD +171 -0
- avrotize-2.21.1.dist-info/WHEEL +4 -0
- avrotize-2.21.1.dist-info/entry_points.txt +3 -0
- avrotize-2.21.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
# pylint: disable=line-too-long
|
|
2
|
+
|
|
3
|
+
""" StructureToTypeScript class for converting JSON Structure schema to TypeScript classes """
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import random
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any, Dict, List, Set, Tuple, Union, Optional
|
|
10
|
+
|
|
11
|
+
from avrotize.common import pascal, process_template
|
|
12
|
+
|
|
13
|
+
JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
|
|
14
|
+
|
|
15
|
+
INDENT = ' '
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def is_typescript_reserved_word(word: str) -> bool:
|
|
19
|
+
"""Checks if a word is a TypeScript reserved word"""
|
|
20
|
+
reserved_words = [
|
|
21
|
+
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
22
|
+
'default', 'delete', 'do', 'else', 'export', 'extends', 'finally',
|
|
23
|
+
'for', 'function', 'if', 'import', 'in', 'instanceof', 'new', 'return',
|
|
24
|
+
'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
|
|
25
|
+
'while', 'with', 'yield', 'enum', 'string', 'number', 'boolean', 'symbol',
|
|
26
|
+
'type', 'namespace', 'module', 'declare', 'abstract', 'readonly',
|
|
27
|
+
]
|
|
28
|
+
return word in reserved_words
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class StructureToTypeScript:
|
|
32
|
+
""" Converts JSON Structure schema to TypeScript classes """
|
|
33
|
+
|
|
34
|
+
def __init__(self, base_package: str = '', typedjson_annotation=False, avro_annotation=False) -> None:
|
|
35
|
+
self.base_package = base_package
|
|
36
|
+
self.typedjson_annotation = typedjson_annotation
|
|
37
|
+
self.avro_annotation = avro_annotation
|
|
38
|
+
self.output_dir = os.getcwd()
|
|
39
|
+
self.schema_doc: JsonNode = None
|
|
40
|
+
self.generated_types: Dict[str, str] = {}
|
|
41
|
+
self.generated_structure_types: Dict[str, Dict[str, Union[str, Dict, List]]] = {}
|
|
42
|
+
self.type_dict: Dict[str, Dict] = {}
|
|
43
|
+
self.definitions: Dict[str, Any] = {}
|
|
44
|
+
self.schema_registry: Dict[str, Dict] = {}
|
|
45
|
+
|
|
46
|
+
def get_qualified_name(self, namespace: str, name: str) -> str:
|
|
47
|
+
""" Concatenates namespace and name with a dot separator """
|
|
48
|
+
return f"{namespace}.{name}" if namespace != '' else name
|
|
49
|
+
|
|
50
|
+
def concat_namespace(self, namespace: str, name: str) -> str:
|
|
51
|
+
""" Concatenates namespace and name with a dot separator """
|
|
52
|
+
if namespace and name:
|
|
53
|
+
return f"{namespace}.{name}"
|
|
54
|
+
elif namespace:
|
|
55
|
+
return namespace
|
|
56
|
+
else:
|
|
57
|
+
return name
|
|
58
|
+
|
|
59
|
+
def map_primitive_to_typescript(self, structure_type: str) -> str:
|
|
60
|
+
""" Maps JSON Structure primitive types to TypeScript types """
|
|
61
|
+
mapping = {
|
|
62
|
+
'null': 'null',
|
|
63
|
+
'boolean': 'boolean',
|
|
64
|
+
'string': 'string',
|
|
65
|
+
'integer': 'number',
|
|
66
|
+
'number': 'number',
|
|
67
|
+
'int8': 'number',
|
|
68
|
+
'uint8': 'number',
|
|
69
|
+
'int16': 'number',
|
|
70
|
+
'uint16': 'number',
|
|
71
|
+
'int32': 'number',
|
|
72
|
+
'uint32': 'number',
|
|
73
|
+
'int64': 'number',
|
|
74
|
+
'uint64': 'number',
|
|
75
|
+
'int128': 'bigint',
|
|
76
|
+
'uint128': 'bigint',
|
|
77
|
+
'float8': 'number',
|
|
78
|
+
'float': 'number',
|
|
79
|
+
'double': 'number',
|
|
80
|
+
'binary32': 'number',
|
|
81
|
+
'binary64': 'number',
|
|
82
|
+
'decimal': 'string',
|
|
83
|
+
'binary': 'string',
|
|
84
|
+
'bytes': 'string',
|
|
85
|
+
'date': 'Date',
|
|
86
|
+
'time': 'Date',
|
|
87
|
+
'datetime': 'Date',
|
|
88
|
+
'timestamp': 'Date',
|
|
89
|
+
'duration': 'string',
|
|
90
|
+
'uuid': 'string',
|
|
91
|
+
'uri': 'string',
|
|
92
|
+
'jsonpointer': 'string',
|
|
93
|
+
'any': 'any'
|
|
94
|
+
}
|
|
95
|
+
qualified_class_name = self.get_qualified_name(
|
|
96
|
+
self.base_package.lower(), structure_type.lower())
|
|
97
|
+
if qualified_class_name in self.generated_types:
|
|
98
|
+
result = qualified_class_name
|
|
99
|
+
else:
|
|
100
|
+
result = mapping.get(structure_type, 'any')
|
|
101
|
+
return result
|
|
102
|
+
|
|
103
|
+
def is_typescript_primitive(self, type_name: str) -> bool:
|
|
104
|
+
""" Checks if a type is a TypeScript primitive type """
|
|
105
|
+
return type_name in ['null', 'boolean', 'number', 'bigint', 'string', 'Date', 'any']
|
|
106
|
+
|
|
107
|
+
def safe_name(self, name: str) -> str:
|
|
108
|
+
"""Converts a name to a safe TypeScript name"""
|
|
109
|
+
if is_typescript_reserved_word(name):
|
|
110
|
+
return name + "_"
|
|
111
|
+
return name
|
|
112
|
+
|
|
113
|
+
def pascal_type_name(self, ref: str) -> str:
|
|
114
|
+
"""Converts a reference to a type name"""
|
|
115
|
+
return '_'.join([pascal(part) for part in ref.split('.')[-1].split('_')])
|
|
116
|
+
|
|
117
|
+
def typescript_package_from_structure_type(self, namespace: str, type_name: str) -> str:
|
|
118
|
+
"""Gets the TypeScript package from a type name"""
|
|
119
|
+
if '.' in type_name:
|
|
120
|
+
# Type name contains dots, use it as package path
|
|
121
|
+
type_name_package = '.'.join([part.lower() for part in type_name.split('.')])
|
|
122
|
+
package = type_name_package
|
|
123
|
+
else:
|
|
124
|
+
# Use namespace as package, don't add type name to package
|
|
125
|
+
namespace_package = '.'.join([part.lower() for part in namespace.split('.')]) if namespace else ''
|
|
126
|
+
package = namespace_package
|
|
127
|
+
if self.base_package:
|
|
128
|
+
package = self.base_package + ('.' if package else '') + package
|
|
129
|
+
return package
|
|
130
|
+
|
|
131
|
+
def typescript_type_from_structure_type(self, type_name: str) -> str:
|
|
132
|
+
"""Gets the TypeScript class from a type name"""
|
|
133
|
+
return self.pascal_type_name(type_name)
|
|
134
|
+
|
|
135
|
+
def typescript_fully_qualified_name_from_structure_type(self, namespace: str, type_name: str) -> str:
|
|
136
|
+
"""Gets the fully qualified TypeScript class name from a Structure type."""
|
|
137
|
+
package = self.typescript_package_from_structure_type(namespace, type_name)
|
|
138
|
+
return package + ('.' if package else '') + self.typescript_type_from_structure_type(type_name)
|
|
139
|
+
|
|
140
|
+
def strip_package_from_fully_qualified_name(self, fully_qualified_name: str) -> str:
|
|
141
|
+
"""Strips the package from a fully qualified name"""
|
|
142
|
+
return fully_qualified_name.split('.')[-1]
|
|
143
|
+
|
|
144
|
+
def strip_nullable(self, ts_type: str) -> str:
|
|
145
|
+
"""Strip nullable marker from TypeScript type"""
|
|
146
|
+
if ts_type.endswith(' | null') or ts_type.endswith('| null'):
|
|
147
|
+
return ts_type.replace(' | null', '').replace('| null', '')
|
|
148
|
+
if ts_type.endswith('| undefined') or ts_type.endswith(' | undefined'):
|
|
149
|
+
return ts_type.replace(' | undefined', '').replace('| undefined', '')
|
|
150
|
+
return ts_type
|
|
151
|
+
|
|
152
|
+
def resolve_ref(self, ref: str, context_schema: Optional[Dict] = None) -> Optional[Dict]:
|
|
153
|
+
""" Resolves a $ref to the actual schema definition """
|
|
154
|
+
if not ref.startswith('#/'):
|
|
155
|
+
if ref in self.schema_registry:
|
|
156
|
+
return self.schema_registry[ref]
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
path = ref[2:].split('/')
|
|
160
|
+
schema = context_schema if context_schema else self.schema_doc
|
|
161
|
+
for part in path:
|
|
162
|
+
if not isinstance(schema, dict) or part not in schema:
|
|
163
|
+
return None
|
|
164
|
+
schema = schema[part]
|
|
165
|
+
return schema
|
|
166
|
+
|
|
167
|
+
def register_schema_ids(self, schema: Dict, base_uri: str = '') -> None:
|
|
168
|
+
""" Recursively registers schemas with $id keywords """
|
|
169
|
+
if not isinstance(schema, dict):
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
if '$id' in schema:
|
|
173
|
+
schema_id = schema['$id']
|
|
174
|
+
if base_uri and not schema_id.startswith(('http://', 'https://', 'urn:')):
|
|
175
|
+
from urllib.parse import urljoin
|
|
176
|
+
schema_id = urljoin(base_uri, schema_id)
|
|
177
|
+
self.schema_registry[schema_id] = schema
|
|
178
|
+
base_uri = schema_id
|
|
179
|
+
|
|
180
|
+
if 'definitions' in schema:
|
|
181
|
+
for def_name, def_schema in schema['definitions'].items():
|
|
182
|
+
if isinstance(def_schema, dict):
|
|
183
|
+
self.register_schema_ids(def_schema, base_uri)
|
|
184
|
+
|
|
185
|
+
if 'properties' in schema:
|
|
186
|
+
for prop_name, prop_schema in schema['properties'].items():
|
|
187
|
+
if isinstance(prop_schema, dict):
|
|
188
|
+
self.register_schema_ids(prop_schema, base_uri)
|
|
189
|
+
|
|
190
|
+
for key in ['items', 'values', 'additionalProperties']:
|
|
191
|
+
if key in schema and isinstance(schema[key], dict):
|
|
192
|
+
self.register_schema_ids(schema[key], base_uri)
|
|
193
|
+
|
|
194
|
+
def convert_structure_type_to_typescript(self, class_name: str, field_name: str,
|
|
195
|
+
structure_type: JsonNode, parent_namespace: str,
|
|
196
|
+
import_types: Set[str]) -> str:
|
|
197
|
+
""" Converts JSON Structure type to TypeScript type """
|
|
198
|
+
if isinstance(structure_type, str):
|
|
199
|
+
ts_type = self.map_primitive_to_typescript(structure_type)
|
|
200
|
+
return ts_type
|
|
201
|
+
elif isinstance(structure_type, list):
|
|
202
|
+
# Handle type unions
|
|
203
|
+
non_null_types = [t for t in structure_type if t != 'null']
|
|
204
|
+
if len(non_null_types) == 1:
|
|
205
|
+
inner_type = self.convert_structure_type_to_typescript(
|
|
206
|
+
class_name, field_name, non_null_types[0], parent_namespace, import_types)
|
|
207
|
+
if 'null' in structure_type:
|
|
208
|
+
return f'{inner_type} | null'
|
|
209
|
+
return inner_type
|
|
210
|
+
else:
|
|
211
|
+
union_types = [self.convert_structure_type_to_typescript(
|
|
212
|
+
class_name, field_name, t, parent_namespace, import_types) for t in non_null_types]
|
|
213
|
+
result = ' | '.join(union_types)
|
|
214
|
+
if 'null' in structure_type:
|
|
215
|
+
result += ' | null'
|
|
216
|
+
return result
|
|
217
|
+
elif isinstance(structure_type, dict):
|
|
218
|
+
# Handle $ref
|
|
219
|
+
if '$ref' in structure_type:
|
|
220
|
+
ref_schema = self.resolve_ref(structure_type['$ref'], self.schema_doc)
|
|
221
|
+
if ref_schema:
|
|
222
|
+
ref_path = structure_type['$ref'].split('/')
|
|
223
|
+
type_name = ref_path[-1]
|
|
224
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
|
|
225
|
+
ref = self.generate_class_or_choice(ref_schema, ref_namespace, write_file=True, explicit_name=type_name)
|
|
226
|
+
import_types.add(ref)
|
|
227
|
+
return self.strip_package_from_fully_qualified_name(ref)
|
|
228
|
+
return 'any'
|
|
229
|
+
|
|
230
|
+
# Handle enum keyword
|
|
231
|
+
if 'enum' in structure_type:
|
|
232
|
+
enum_ref = self.generate_enum(structure_type, field_name, parent_namespace, write_file=True)
|
|
233
|
+
import_types.add(enum_ref)
|
|
234
|
+
return self.strip_package_from_fully_qualified_name(enum_ref)
|
|
235
|
+
|
|
236
|
+
# Handle type keyword
|
|
237
|
+
if 'type' not in structure_type:
|
|
238
|
+
return 'any'
|
|
239
|
+
|
|
240
|
+
struct_type = structure_type['type']
|
|
241
|
+
|
|
242
|
+
# Handle complex types
|
|
243
|
+
if struct_type == 'object':
|
|
244
|
+
class_ref = self.generate_class(structure_type, parent_namespace, write_file=True)
|
|
245
|
+
import_types.add(class_ref)
|
|
246
|
+
return self.strip_package_from_fully_qualified_name(class_ref)
|
|
247
|
+
elif struct_type == 'array':
|
|
248
|
+
items_type = self.convert_structure_type_to_typescript(
|
|
249
|
+
class_name, field_name+'Array', structure_type.get('items', {'type': 'any'}),
|
|
250
|
+
parent_namespace, import_types)
|
|
251
|
+
return f"{items_type}[]"
|
|
252
|
+
elif struct_type == 'set':
|
|
253
|
+
items_type = self.convert_structure_type_to_typescript(
|
|
254
|
+
class_name, field_name+'Set', structure_type.get('items', {'type': 'any'}),
|
|
255
|
+
parent_namespace, import_types)
|
|
256
|
+
return f"Set<{items_type}>"
|
|
257
|
+
elif struct_type == 'map':
|
|
258
|
+
values_type = self.convert_structure_type_to_typescript(
|
|
259
|
+
class_name, field_name+'Map', structure_type.get('values', {'type': 'any'}),
|
|
260
|
+
parent_namespace, import_types)
|
|
261
|
+
return f"{{ [key: string]: {values_type} }}"
|
|
262
|
+
elif struct_type == 'choice':
|
|
263
|
+
# Generate choice returns a Union type and populates import_types with the choice types
|
|
264
|
+
return self.generate_choice(structure_type, parent_namespace, write_file=True, import_types=import_types)
|
|
265
|
+
elif struct_type == 'tuple':
|
|
266
|
+
tuple_ref = self.generate_tuple(structure_type, parent_namespace, write_file=True)
|
|
267
|
+
import_types.add(tuple_ref)
|
|
268
|
+
return self.strip_package_from_fully_qualified_name(tuple_ref)
|
|
269
|
+
else:
|
|
270
|
+
return self.convert_structure_type_to_typescript(class_name, field_name, struct_type, parent_namespace, import_types)
|
|
271
|
+
return 'any'
|
|
272
|
+
|
|
273
|
+
def generate_class_or_choice(self, structure_schema: Dict, parent_namespace: str,
|
|
274
|
+
write_file: bool = True, explicit_name: str = '') -> str:
|
|
275
|
+
""" Generates a Class or Choice """
|
|
276
|
+
struct_type = structure_schema.get('type', 'object')
|
|
277
|
+
if struct_type == 'object':
|
|
278
|
+
return self.generate_class(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
279
|
+
elif struct_type == 'choice':
|
|
280
|
+
return self.generate_choice(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
281
|
+
elif struct_type == 'tuple':
|
|
282
|
+
return self.generate_tuple(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
|
|
283
|
+
return 'any'
|
|
284
|
+
|
|
285
|
+
def generate_class(self, structure_schema: Dict, parent_namespace: str,
|
|
286
|
+
write_file: bool, explicit_name: str = '') -> str:
|
|
287
|
+
""" Generates a TypeScript class/interface from JSON Structure object type """
|
|
288
|
+
import_types: Set[str] = set()
|
|
289
|
+
|
|
290
|
+
# Get name and namespace
|
|
291
|
+
class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedClass'))
|
|
292
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
293
|
+
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
294
|
+
typescript_qualified_name = self.typescript_fully_qualified_name_from_structure_type(schema_namespace, class_name)
|
|
295
|
+
|
|
296
|
+
if typescript_qualified_name in self.generated_types:
|
|
297
|
+
return typescript_qualified_name
|
|
298
|
+
|
|
299
|
+
# Check if this is an abstract type
|
|
300
|
+
is_abstract = structure_schema.get('abstract', False)
|
|
301
|
+
|
|
302
|
+
# Handle inheritance ($extends)
|
|
303
|
+
base_class = None
|
|
304
|
+
if '$extends' in structure_schema:
|
|
305
|
+
base_ref = structure_schema['$extends']
|
|
306
|
+
if isinstance(self.schema_doc, dict):
|
|
307
|
+
base_schema = self.resolve_ref(base_ref, self.schema_doc)
|
|
308
|
+
if base_schema:
|
|
309
|
+
base_class_ref = self.generate_class(base_schema, parent_namespace, write_file=True)
|
|
310
|
+
base_class = self.strip_package_from_fully_qualified_name(base_class_ref)
|
|
311
|
+
import_types.add(base_class_ref)
|
|
312
|
+
|
|
313
|
+
# Collect properties
|
|
314
|
+
properties = structure_schema.get('properties', {})
|
|
315
|
+
required_props = set(structure_schema.get('required', []))
|
|
316
|
+
|
|
317
|
+
# Handle add-ins ($uses)
|
|
318
|
+
if '$uses' in structure_schema and isinstance(structure_schema['$uses'], list):
|
|
319
|
+
for addin_ref in structure_schema['$uses']:
|
|
320
|
+
if isinstance(addin_ref, str):
|
|
321
|
+
# Resolve the add-in reference
|
|
322
|
+
addin_schema = self.resolve_ref(addin_ref, self.schema_doc)
|
|
323
|
+
if addin_schema and 'properties' in addin_schema:
|
|
324
|
+
properties.update(addin_schema['properties'])
|
|
325
|
+
if 'required' in addin_schema:
|
|
326
|
+
required_props.update(addin_schema['required'])
|
|
327
|
+
|
|
328
|
+
# Generate fields
|
|
329
|
+
fields = []
|
|
330
|
+
for prop_name, prop_schema in properties.items():
|
|
331
|
+
field_type = self.convert_structure_type_to_typescript(
|
|
332
|
+
class_name, prop_name, prop_schema, namespace, import_types)
|
|
333
|
+
is_required = prop_name in required_props
|
|
334
|
+
is_optional = not is_required
|
|
335
|
+
field_type_no_null = self.strip_nullable(field_type)
|
|
336
|
+
|
|
337
|
+
# Check if the field type is an enum
|
|
338
|
+
is_enum = False
|
|
339
|
+
for import_type in import_types:
|
|
340
|
+
if import_type.endswith('.' + field_type_no_null) or import_type == field_type_no_null:
|
|
341
|
+
if import_type in self.generated_types and self.generated_types[import_type] == 'enum':
|
|
342
|
+
is_enum = True
|
|
343
|
+
break
|
|
344
|
+
|
|
345
|
+
fields.append({
|
|
346
|
+
'name': self.safe_name(prop_name),
|
|
347
|
+
'original_name': prop_name,
|
|
348
|
+
'type': field_type,
|
|
349
|
+
'type_no_null': field_type_no_null,
|
|
350
|
+
'is_required': is_required,
|
|
351
|
+
'is_optional': is_optional,
|
|
352
|
+
'is_primitive': self.is_typescript_primitive(field_type_no_null.replace('[]', '')),
|
|
353
|
+
'is_enum': is_enum,
|
|
354
|
+
'docstring': prop_schema.get('description', '') if isinstance(prop_schema, dict) else ''
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
# Build imports
|
|
358
|
+
imports_with_paths: Dict[str, str] = {}
|
|
359
|
+
for import_type in import_types:
|
|
360
|
+
if import_type == typescript_qualified_name:
|
|
361
|
+
continue
|
|
362
|
+
import_is_enum = import_type in self.generated_types and self.generated_types[import_type] == 'enum'
|
|
363
|
+
import_type_parts = import_type.split('.')
|
|
364
|
+
import_type_name = pascal(import_type_parts[-1])
|
|
365
|
+
import_path = '/'.join(import_type_parts)
|
|
366
|
+
current_path = '/'.join(namespace.split('.'))
|
|
367
|
+
relative_import_path = os.path.relpath(import_path, current_path).replace(os.sep, '/')
|
|
368
|
+
if not relative_import_path.startswith('.'):
|
|
369
|
+
relative_import_path = f'./{relative_import_path}'
|
|
370
|
+
imports_with_paths[import_type_name] = relative_import_path + '.js'
|
|
371
|
+
|
|
372
|
+
# Prepare required fields with test values for createInstance()
|
|
373
|
+
required_fields = [f for f in fields if f.get('is_required', not f.get('is_optional', False))]
|
|
374
|
+
for field in required_fields:
|
|
375
|
+
field['test_value'] = self.generate_test_value(field)
|
|
376
|
+
|
|
377
|
+
# Generate class definition using template
|
|
378
|
+
class_definition = process_template(
|
|
379
|
+
"structuretots/class_core.ts.jinja",
|
|
380
|
+
namespace=namespace,
|
|
381
|
+
class_name=class_name,
|
|
382
|
+
base_class=base_class,
|
|
383
|
+
is_abstract=is_abstract,
|
|
384
|
+
docstring=structure_schema.get('description', '').strip() if 'description' in structure_schema else f'A {class_name} class.',
|
|
385
|
+
fields=fields,
|
|
386
|
+
required_fields=required_fields,
|
|
387
|
+
imports=imports_with_paths,
|
|
388
|
+
typedjson_annotation=self.typedjson_annotation,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
if write_file:
|
|
392
|
+
self.write_to_file(namespace, class_name, class_definition)
|
|
393
|
+
# Generate test class
|
|
394
|
+
if not is_abstract: # Don't generate tests for abstract classes
|
|
395
|
+
self.generate_test_class(namespace, class_name, fields)
|
|
396
|
+
self.generated_types[typescript_qualified_name] = 'class'
|
|
397
|
+
return typescript_qualified_name
|
|
398
|
+
|
|
399
|
+
def generate_enum(self, structure_schema: Dict, field_name: str, parent_namespace: str,
|
|
400
|
+
write_file: bool = True) -> str:
|
|
401
|
+
""" Generates a TypeScript enum from JSON Structure enum """
|
|
402
|
+
enum_name = pascal(structure_schema.get('name', field_name + 'Enum'))
|
|
403
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
404
|
+
namespace = self.concat_namespace(self.base_package, schema_namespace).lower()
|
|
405
|
+
# Use schema_namespace (not parent_namespace) to match the file location
|
|
406
|
+
typescript_qualified_name = self.typescript_fully_qualified_name_from_structure_type(schema_namespace, enum_name)
|
|
407
|
+
|
|
408
|
+
if typescript_qualified_name in self.generated_types:
|
|
409
|
+
return typescript_qualified_name
|
|
410
|
+
|
|
411
|
+
symbols = structure_schema.get('enum', [])
|
|
412
|
+
|
|
413
|
+
enum_definition = process_template(
|
|
414
|
+
"structuretots/enum_core.ts.jinja",
|
|
415
|
+
namespace=namespace,
|
|
416
|
+
enum_name=enum_name,
|
|
417
|
+
docstring=structure_schema.get('description', '').strip() if 'description' in structure_schema else f'A {enum_name} enum.',
|
|
418
|
+
symbols=symbols,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if write_file:
|
|
422
|
+
self.write_to_file(namespace, enum_name, enum_definition)
|
|
423
|
+
self.generated_types[typescript_qualified_name] = 'enum'
|
|
424
|
+
return typescript_qualified_name
|
|
425
|
+
|
|
426
|
+
def generate_choice(self, structure_schema: Dict, parent_namespace: str,
|
|
427
|
+
write_file: bool = True, explicit_name: str = '',
|
|
428
|
+
import_types: Optional[Set[str]] = None) -> str:
|
|
429
|
+
""" Generates a TypeScript union type from JSON Structure choice type """
|
|
430
|
+
if import_types is None:
|
|
431
|
+
import_types = set()
|
|
432
|
+
|
|
433
|
+
choice_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'Choice'))
|
|
434
|
+
schema_namespace = structure_schema.get('namespace', parent_namespace)
|
|
435
|
+
|
|
436
|
+
# If the choice extends a base class, generate the base class first
|
|
437
|
+
if '$extends' in structure_schema:
|
|
438
|
+
base_ref = structure_schema['$extends']
|
|
439
|
+
if isinstance(self.schema_doc, dict):
|
|
440
|
+
base_schema = self.resolve_ref(base_ref, self.schema_doc)
|
|
441
|
+
if base_schema:
|
|
442
|
+
# Generate the base class
|
|
443
|
+
ref_path = base_ref.split('/')
|
|
444
|
+
base_name = ref_path[-1]
|
|
445
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
|
|
446
|
+
self.generate_class(base_schema, ref_namespace, write_file=True, explicit_name=base_name)
|
|
447
|
+
|
|
448
|
+
# Generate types for each choice
|
|
449
|
+
choice_types = []
|
|
450
|
+
choices = structure_schema.get('choices', {})
|
|
451
|
+
|
|
452
|
+
for choice_key, choice_schema in choices.items():
|
|
453
|
+
if isinstance(choice_schema, dict):
|
|
454
|
+
if '$ref' in choice_schema:
|
|
455
|
+
# Resolve reference and generate the type
|
|
456
|
+
ref_schema = self.resolve_ref(choice_schema['$ref'], self.schema_doc if isinstance(self.schema_doc, dict) else None)
|
|
457
|
+
if ref_schema:
|
|
458
|
+
ref_path = choice_schema['$ref'].split('/')
|
|
459
|
+
ref_name = ref_path[-1]
|
|
460
|
+
ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
|
|
461
|
+
qualified_name = self.generate_class(ref_schema, ref_namespace, write_file=True, explicit_name=ref_name)
|
|
462
|
+
import_types.add(qualified_name)
|
|
463
|
+
choice_types.append(qualified_name.split('.')[-1])
|
|
464
|
+
elif 'type' in choice_schema:
|
|
465
|
+
# Generate inline type
|
|
466
|
+
ts_type = self.convert_structure_type_to_typescript(choice_name, choice_key, choice_schema, schema_namespace, import_types)
|
|
467
|
+
choice_types.append(ts_type)
|
|
468
|
+
|
|
469
|
+
# Return Union type
|
|
470
|
+
if len(choice_types) == 0:
|
|
471
|
+
return 'any'
|
|
472
|
+
elif len(choice_types) == 1:
|
|
473
|
+
return choice_types[0]
|
|
474
|
+
else:
|
|
475
|
+
return ' | '.join(choice_types)
|
|
476
|
+
|
|
477
|
+
def generate_tuple(self, structure_schema: Dict, parent_namespace: str,
|
|
478
|
+
write_file: bool = True, explicit_name: str = '') -> str:
|
|
479
|
+
""" Generates a TypeScript tuple type from JSON Structure tuple type """
|
|
480
|
+
tuple_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'Tuple'))
|
|
481
|
+
namespace = self.concat_namespace(self.base_package, structure_schema.get('namespace', parent_namespace)).lower()
|
|
482
|
+
typescript_qualified_name = self.typescript_fully_qualified_name_from_structure_type(parent_namespace, tuple_name)
|
|
483
|
+
|
|
484
|
+
if typescript_qualified_name in self.generated_types:
|
|
485
|
+
return typescript_qualified_name
|
|
486
|
+
|
|
487
|
+
import_types: Set[str] = set()
|
|
488
|
+
tuple_items = structure_schema.get('items', [])
|
|
489
|
+
item_types = []
|
|
490
|
+
for idx, item in enumerate(tuple_items):
|
|
491
|
+
item_type = self.convert_structure_type_to_typescript(
|
|
492
|
+
tuple_name, f'item{idx}', item, namespace, import_types)
|
|
493
|
+
item_types.append(item_type)
|
|
494
|
+
|
|
495
|
+
# TypeScript tuples are just arrays with fixed length and types
|
|
496
|
+
tuple_type = f"[{', '.join(item_types)}]"
|
|
497
|
+
|
|
498
|
+
# Generate type alias
|
|
499
|
+
tuple_definition = f"export type {tuple_name} = {tuple_type};\n"
|
|
500
|
+
|
|
501
|
+
if write_file:
|
|
502
|
+
self.write_to_file(namespace, tuple_name, tuple_definition)
|
|
503
|
+
self.generated_types[typescript_qualified_name] = 'tuple'
|
|
504
|
+
return typescript_qualified_name
|
|
505
|
+
|
|
506
|
+
def generate_test_value(self, field: Dict) -> str:
|
|
507
|
+
"""Generates a test value for a given field in TypeScript"""
|
|
508
|
+
import random
|
|
509
|
+
field_type = field['type_no_null']
|
|
510
|
+
|
|
511
|
+
# Map TypeScript types to test values
|
|
512
|
+
test_values = {
|
|
513
|
+
'string': '"test_string"',
|
|
514
|
+
'number': str(random.randint(1, 100)),
|
|
515
|
+
'bigint': 'BigInt(123)',
|
|
516
|
+
'boolean': str(random.choice(['true', 'false'])).lower(),
|
|
517
|
+
'Date': 'new Date()',
|
|
518
|
+
'any': '{ test: "data" }',
|
|
519
|
+
'null': 'null'
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
# Handle arrays
|
|
523
|
+
if field_type.endswith('[]'):
|
|
524
|
+
inner_type = field_type[:-2]
|
|
525
|
+
if inner_type in test_values:
|
|
526
|
+
return f"[{test_values[inner_type]}]"
|
|
527
|
+
else:
|
|
528
|
+
# For custom types, create empty array
|
|
529
|
+
return f"[]"
|
|
530
|
+
|
|
531
|
+
# Handle Set
|
|
532
|
+
if field_type.startswith('Set<'):
|
|
533
|
+
inner_type = field_type[4:-1]
|
|
534
|
+
if inner_type in test_values:
|
|
535
|
+
return f"new Set([{test_values[inner_type]}])"
|
|
536
|
+
else:
|
|
537
|
+
return f"new Set()"
|
|
538
|
+
|
|
539
|
+
# Handle maps (objects with string index signature)
|
|
540
|
+
if field_type.startswith('{ [key: string]:'):
|
|
541
|
+
return '{}'
|
|
542
|
+
|
|
543
|
+
# Handle enums - use first value with Object.values()
|
|
544
|
+
if field.get('is_enum', False):
|
|
545
|
+
return f'Object.values({field_type})[0] as {field_type}'
|
|
546
|
+
|
|
547
|
+
# Return test value for primitives, or call createInstance() for complex types (classes)
|
|
548
|
+
return test_values.get(field_type, f'{field_type}.createInstance()')
|
|
549
|
+
|
|
550
|
+
def generate_test_class(self, namespace: str, class_name: str, fields: List[Dict[str, Any]]) -> None:
|
|
551
|
+
"""Generates a unit test class for a TypeScript class"""
|
|
552
|
+
# Get only required fields for the test
|
|
553
|
+
required_fields = [f for f in fields if f['is_required']]
|
|
554
|
+
|
|
555
|
+
# Collect enum imports needed for test file
|
|
556
|
+
enum_imports: Dict[str, str] = {}
|
|
557
|
+
|
|
558
|
+
# Generate test values for required fields
|
|
559
|
+
for field in required_fields:
|
|
560
|
+
field['test_value'] = self.generate_test_value(field)
|
|
561
|
+
# Check if this field is an enum and needs an import
|
|
562
|
+
if field.get('is_enum', False):
|
|
563
|
+
enum_type = field['type_no_null']
|
|
564
|
+
# Find the enum in generated_types to get its full path
|
|
565
|
+
for qualified_name, type_kind in self.generated_types.items():
|
|
566
|
+
if type_kind == 'enum' and qualified_name.endswith('.' + enum_type):
|
|
567
|
+
# Build import path - lowercase namespace like write_to_file does
|
|
568
|
+
parts = qualified_name.split('.')
|
|
569
|
+
enum_namespace = '.'.join(parts[:-1]).lower()
|
|
570
|
+
enum_import_path = enum_namespace.replace('.', '/') + '/' + enum_type
|
|
571
|
+
enum_imports[enum_type] = f'../src/{enum_import_path}'
|
|
572
|
+
break
|
|
573
|
+
|
|
574
|
+
# Determine relative path from test directory to src
|
|
575
|
+
namespace_path = namespace.replace('.', '/') if namespace else ''
|
|
576
|
+
if namespace_path:
|
|
577
|
+
relative_path = f"{namespace_path}/{class_name}"
|
|
578
|
+
else:
|
|
579
|
+
relative_path = class_name
|
|
580
|
+
|
|
581
|
+
test_class_definition = process_template(
|
|
582
|
+
"structuretots/test_class.ts.jinja",
|
|
583
|
+
class_name=class_name,
|
|
584
|
+
required_fields=required_fields,
|
|
585
|
+
relative_path=relative_path,
|
|
586
|
+
enum_imports=enum_imports
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
# Write test file
|
|
590
|
+
test_dir = os.path.join(self.output_dir, "test")
|
|
591
|
+
os.makedirs(test_dir, exist_ok=True)
|
|
592
|
+
|
|
593
|
+
test_file_path = os.path.join(test_dir, f"{class_name}.test.ts")
|
|
594
|
+
with open(test_file_path, 'w', encoding='utf-8') as f:
|
|
595
|
+
f.write(test_class_definition)
|
|
596
|
+
|
|
597
|
+
def write_to_file(self, namespace: str, type_name: str, content: str) -> None:
|
|
598
|
+
""" Writes generated content to a TypeScript file """
|
|
599
|
+
namespace_path = namespace.replace('.', '/')
|
|
600
|
+
file_dir = os.path.join(self.output_dir, 'src', namespace_path)
|
|
601
|
+
os.makedirs(file_dir, exist_ok=True)
|
|
602
|
+
|
|
603
|
+
file_path = os.path.join(file_dir, f'{type_name}.ts')
|
|
604
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
605
|
+
f.write(content)
|
|
606
|
+
|
|
607
|
+
def generate_package_json(self, package_name: str) -> None:
|
|
608
|
+
""" Generates package.json file """
|
|
609
|
+
package_json = process_template(
|
|
610
|
+
"structuretots/package.json.jinja",
|
|
611
|
+
package_name=package_name or 'generated-types',
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
with open(os.path.join(self.output_dir, 'package.json'), 'w', encoding='utf-8') as f:
|
|
615
|
+
f.write(package_json)
|
|
616
|
+
|
|
617
|
+
def generate_tsconfig(self) -> None:
|
|
618
|
+
""" Generates tsconfig.json file """
|
|
619
|
+
tsconfig = process_template("structuretots/tsconfig.json.jinja")
|
|
620
|
+
with open(os.path.join(self.output_dir, 'tsconfig.json'), 'w', encoding='utf-8') as f:
|
|
621
|
+
f.write(tsconfig)
|
|
622
|
+
|
|
623
|
+
def generate_gitignore(self) -> None:
|
|
624
|
+
""" Generates .gitignore file """
|
|
625
|
+
gitignore = process_template("structuretots/gitignore.jinja")
|
|
626
|
+
with open(os.path.join(self.output_dir, '.gitignore'), 'w', encoding='utf-8') as f:
|
|
627
|
+
f.write(gitignore)
|
|
628
|
+
|
|
629
|
+
def generate_index(self) -> None:
|
|
630
|
+
""" Generates index.ts that exports all generated types with aliased exports """
|
|
631
|
+
exports = []
|
|
632
|
+
for qualified_name, type_kind in self.generated_types.items():
|
|
633
|
+
# Split the qualified_name into parts
|
|
634
|
+
parts = qualified_name.split('.')
|
|
635
|
+
type_name = parts[-1] # The actual type name
|
|
636
|
+
namespace = '.'.join(parts[:-1]) # The namespace excluding the type
|
|
637
|
+
|
|
638
|
+
# Construct the relative path to the .js file
|
|
639
|
+
if namespace:
|
|
640
|
+
# Lowercase the namespace to match the directory structure created by write_to_file
|
|
641
|
+
relative_path = namespace.lower().replace('.', '/') + '/' + type_name
|
|
642
|
+
else:
|
|
643
|
+
relative_path = type_name
|
|
644
|
+
|
|
645
|
+
if not relative_path.startswith('./'):
|
|
646
|
+
relative_path = './' + relative_path
|
|
647
|
+
|
|
648
|
+
# Construct the alias name by joining all parts with underscores (PascalCase)
|
|
649
|
+
alias_parts = [pascal(part) for part in parts]
|
|
650
|
+
alias_name = '_'.join(alias_parts)
|
|
651
|
+
|
|
652
|
+
# Generate the export statement with alias (like avrotots does)
|
|
653
|
+
exports.append(f"export {{ {type_name} as {alias_name} }} from '{relative_path}.js';")
|
|
654
|
+
|
|
655
|
+
index_content = '\n'.join(exports) + '\n' if exports else ''
|
|
656
|
+
|
|
657
|
+
src_dir = os.path.join(self.output_dir, 'src')
|
|
658
|
+
os.makedirs(src_dir, exist_ok=True)
|
|
659
|
+
with open(os.path.join(src_dir, 'index.ts'), 'w', encoding='utf-8') as f:
|
|
660
|
+
f.write(index_content)
|
|
661
|
+
|
|
662
|
+
def convert(self, structure_schema_path: str, output_dir: str, package_name: str = '') -> None:
|
|
663
|
+
""" Converts a JSON Structure schema file to TypeScript classes """
|
|
664
|
+
self.output_dir = output_dir
|
|
665
|
+
|
|
666
|
+
# Load schema
|
|
667
|
+
with open(structure_schema_path, 'r', encoding='utf-8') as f:
|
|
668
|
+
schema = json.load(f)
|
|
669
|
+
|
|
670
|
+
self.convert_schema(schema, output_dir, package_name)
|
|
671
|
+
|
|
672
|
+
def convert_schema(self, schema: JsonNode, output_dir: str, package_name: str = '') -> None:
|
|
673
|
+
""" Converts a JSON Structure schema (or list of schemas) to TypeScript classes """
|
|
674
|
+
# Normalize to list
|
|
675
|
+
if not isinstance(schema, list):
|
|
676
|
+
schema = [schema]
|
|
677
|
+
|
|
678
|
+
self.output_dir = output_dir
|
|
679
|
+
self.schema_doc = schema
|
|
680
|
+
|
|
681
|
+
# Register schema IDs for all schemas
|
|
682
|
+
for s in schema:
|
|
683
|
+
if isinstance(s, dict):
|
|
684
|
+
self.register_schema_ids(s)
|
|
685
|
+
|
|
686
|
+
# Process each schema
|
|
687
|
+
for s in schema:
|
|
688
|
+
if not isinstance(s, dict):
|
|
689
|
+
continue
|
|
690
|
+
|
|
691
|
+
# Process definitions
|
|
692
|
+
if 'definitions' in s:
|
|
693
|
+
for def_name, def_schema in s['definitions'].items():
|
|
694
|
+
if isinstance(def_schema, dict):
|
|
695
|
+
self.generate_class_or_choice(def_schema, '', write_file=True, explicit_name=def_name)
|
|
696
|
+
|
|
697
|
+
# Process root schema if it's an object or choice
|
|
698
|
+
if 'type' in s:
|
|
699
|
+
root_namespace = s.get('namespace', '')
|
|
700
|
+
self.generate_class_or_choice(s, root_namespace, write_file=True)
|
|
701
|
+
|
|
702
|
+
# Generate project files
|
|
703
|
+
self.generate_package_json(package_name)
|
|
704
|
+
self.generate_tsconfig()
|
|
705
|
+
self.generate_gitignore()
|
|
706
|
+
self.generate_index()
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def convert_structure_to_typescript(structure_schema_path: str, ts_file_path: str,
|
|
710
|
+
package_name: str = '', typedjson_annotation: bool = False,
|
|
711
|
+
avro_annotation: bool = False) -> None:
|
|
712
|
+
"""
|
|
713
|
+
Converts a JSON Structure schema to TypeScript classes.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
structure_schema_path: Path to the JSON Structure schema file
|
|
717
|
+
ts_file_path: Output directory for TypeScript files
|
|
718
|
+
package_name: Package name for the generated TypeScript project
|
|
719
|
+
typedjson_annotation: Whether to include TypedJSON annotations
|
|
720
|
+
avro_annotation: Whether to include Avro annotations
|
|
721
|
+
"""
|
|
722
|
+
converter = StructureToTypeScript(package_name, typedjson_annotation, avro_annotation)
|
|
723
|
+
converter.convert(structure_schema_path, ts_file_path, package_name)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def convert_structure_schema_to_typescript(structure_schema: JsonNode, output_dir: str,
|
|
727
|
+
package_name: str = '', typedjson_annotation: bool = False,
|
|
728
|
+
avro_annotation: bool = False) -> None:
|
|
729
|
+
"""
|
|
730
|
+
Converts a JSON Structure schema to TypeScript classes.
|
|
731
|
+
|
|
732
|
+
Args:
|
|
733
|
+
structure_schema: JSON Structure schema to convert
|
|
734
|
+
output_dir: Output directory for TypeScript files
|
|
735
|
+
package_name: Package name for the generated TypeScript project
|
|
736
|
+
typedjson_annotation: Whether to include TypedJSON annotations
|
|
737
|
+
avro_annotation: Whether to include Avro annotations
|
|
738
|
+
"""
|
|
739
|
+
converter = StructureToTypeScript(package_name, typedjson_annotation, avro_annotation)
|
|
740
|
+
converter.convert_schema(structure_schema, output_dir, package_name)
|