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.
Files changed (171) hide show
  1. avrotize/__init__.py +66 -0
  2. avrotize/__main__.py +6 -0
  3. avrotize/_version.py +34 -0
  4. avrotize/asn1toavro.py +160 -0
  5. avrotize/avrotize.py +152 -0
  6. avrotize/avrotocpp/CMakeLists.txt.jinja +77 -0
  7. avrotize/avrotocpp/build.bat.jinja +7 -0
  8. avrotize/avrotocpp/build.sh.jinja +7 -0
  9. avrotize/avrotocpp/dataclass_body.jinja +108 -0
  10. avrotize/avrotocpp/vcpkg.json.jinja +21 -0
  11. avrotize/avrotocpp.py +483 -0
  12. avrotize/avrotocsharp/README.md.jinja +166 -0
  13. avrotize/avrotocsharp/class_test.cs.jinja +266 -0
  14. avrotize/avrotocsharp/dataclass_core.jinja +293 -0
  15. avrotize/avrotocsharp/enum_test.cs.jinja +20 -0
  16. avrotize/avrotocsharp/project.csproj.jinja +30 -0
  17. avrotize/avrotocsharp/project.sln.jinja +34 -0
  18. avrotize/avrotocsharp/run_coverage.ps1.jinja +98 -0
  19. avrotize/avrotocsharp/run_coverage.sh.jinja +149 -0
  20. avrotize/avrotocsharp/testproject.csproj.jinja +19 -0
  21. avrotize/avrotocsharp.py +1180 -0
  22. avrotize/avrotocsv.py +121 -0
  23. avrotize/avrotodatapackage.py +173 -0
  24. avrotize/avrotodb.py +1383 -0
  25. avrotize/avrotogo/go_enum.jinja +12 -0
  26. avrotize/avrotogo/go_helpers.jinja +31 -0
  27. avrotize/avrotogo/go_struct.jinja +151 -0
  28. avrotize/avrotogo/go_test.jinja +47 -0
  29. avrotize/avrotogo/go_union.jinja +38 -0
  30. avrotize/avrotogo.py +476 -0
  31. avrotize/avrotographql.py +197 -0
  32. avrotize/avrotoiceberg.py +210 -0
  33. avrotize/avrotojava/class_test.java.jinja +212 -0
  34. avrotize/avrotojava/enum_test.java.jinja +21 -0
  35. avrotize/avrotojava/testproject.pom.jinja +54 -0
  36. avrotize/avrotojava.py +2156 -0
  37. avrotize/avrotojs.py +250 -0
  38. avrotize/avrotojsons.py +481 -0
  39. avrotize/avrotojstruct.py +345 -0
  40. avrotize/avrotokusto.py +364 -0
  41. avrotize/avrotomd/README.md.jinja +49 -0
  42. avrotize/avrotomd.py +137 -0
  43. avrotize/avrotools.py +168 -0
  44. avrotize/avrotoparquet.py +208 -0
  45. avrotize/avrotoproto.py +359 -0
  46. avrotize/avrotopython/dataclass_core.jinja +241 -0
  47. avrotize/avrotopython/enum_core.jinja +87 -0
  48. avrotize/avrotopython/pyproject_toml.jinja +18 -0
  49. avrotize/avrotopython/test_class.jinja +97 -0
  50. avrotize/avrotopython/test_enum.jinja +23 -0
  51. avrotize/avrotopython.py +626 -0
  52. avrotize/avrotorust/dataclass_enum.rs.jinja +74 -0
  53. avrotize/avrotorust/dataclass_struct.rs.jinja +204 -0
  54. avrotize/avrotorust/dataclass_union.rs.jinja +105 -0
  55. avrotize/avrotorust.py +435 -0
  56. avrotize/avrotots/class_core.ts.jinja +140 -0
  57. avrotize/avrotots/class_test.ts.jinja +77 -0
  58. avrotize/avrotots/enum_core.ts.jinja +46 -0
  59. avrotize/avrotots/gitignore.jinja +34 -0
  60. avrotize/avrotots/index.ts.jinja +0 -0
  61. avrotize/avrotots/package.json.jinja +23 -0
  62. avrotize/avrotots/tsconfig.json.jinja +21 -0
  63. avrotize/avrotots.py +687 -0
  64. avrotize/avrotoxsd.py +344 -0
  65. avrotize/cddltostructure.py +1841 -0
  66. avrotize/commands.json +3496 -0
  67. avrotize/common.py +834 -0
  68. avrotize/constants.py +87 -0
  69. avrotize/csvtoavro.py +132 -0
  70. avrotize/datapackagetoavro.py +76 -0
  71. avrotize/dependencies/cpp/vcpkg/vcpkg.json +19 -0
  72. avrotize/dependencies/cs/net90/dependencies.csproj +29 -0
  73. avrotize/dependencies/go/go121/go.mod +6 -0
  74. avrotize/dependencies/java/jdk21/pom.xml +91 -0
  75. avrotize/dependencies/python/py312/requirements.txt +13 -0
  76. avrotize/dependencies/rust/stable/Cargo.toml +17 -0
  77. avrotize/dependencies/typescript/node22/package.json +16 -0
  78. avrotize/dependency_resolver.py +348 -0
  79. avrotize/dependency_version.py +432 -0
  80. avrotize/generic/generic.avsc +57 -0
  81. avrotize/jsonstoavro.py +2167 -0
  82. avrotize/jsonstostructure.py +2864 -0
  83. avrotize/jstructtoavro.py +878 -0
  84. avrotize/kstructtoavro.py +93 -0
  85. avrotize/kustotoavro.py +455 -0
  86. avrotize/openapitostructure.py +717 -0
  87. avrotize/parquettoavro.py +157 -0
  88. avrotize/proto2parser.py +498 -0
  89. avrotize/proto3parser.py +403 -0
  90. avrotize/prototoavro.py +382 -0
  91. avrotize/prototypes/any.avsc +19 -0
  92. avrotize/prototypes/api.avsc +106 -0
  93. avrotize/prototypes/duration.avsc +20 -0
  94. avrotize/prototypes/field_mask.avsc +18 -0
  95. avrotize/prototypes/struct.avsc +60 -0
  96. avrotize/prototypes/timestamp.avsc +20 -0
  97. avrotize/prototypes/type.avsc +253 -0
  98. avrotize/prototypes/wrappers.avsc +117 -0
  99. avrotize/structuretocddl.py +597 -0
  100. avrotize/structuretocpp/CMakeLists.txt.jinja +76 -0
  101. avrotize/structuretocpp/build.bat.jinja +3 -0
  102. avrotize/structuretocpp/build.sh.jinja +3 -0
  103. avrotize/structuretocpp/dataclass_body.jinja +50 -0
  104. avrotize/structuretocpp/vcpkg.json.jinja +11 -0
  105. avrotize/structuretocpp.py +697 -0
  106. avrotize/structuretocsharp/class_test.cs.jinja +180 -0
  107. avrotize/structuretocsharp/dataclass_core.jinja +156 -0
  108. avrotize/structuretocsharp/enum_test.cs.jinja +36 -0
  109. avrotize/structuretocsharp/json_structure_converters.cs.jinja +399 -0
  110. avrotize/structuretocsharp/program.cs.jinja +49 -0
  111. avrotize/structuretocsharp/project.csproj.jinja +17 -0
  112. avrotize/structuretocsharp/project.sln.jinja +34 -0
  113. avrotize/structuretocsharp/testproject.csproj.jinja +18 -0
  114. avrotize/structuretocsharp/tuple_converter.cs.jinja +121 -0
  115. avrotize/structuretocsharp.py +2295 -0
  116. avrotize/structuretocsv.py +365 -0
  117. avrotize/structuretodatapackage.py +659 -0
  118. avrotize/structuretodb.py +1125 -0
  119. avrotize/structuretogo/go_enum.jinja +12 -0
  120. avrotize/structuretogo/go_helpers.jinja +26 -0
  121. avrotize/structuretogo/go_interface.jinja +18 -0
  122. avrotize/structuretogo/go_struct.jinja +187 -0
  123. avrotize/structuretogo/go_test.jinja +70 -0
  124. avrotize/structuretogo.py +729 -0
  125. avrotize/structuretographql.py +502 -0
  126. avrotize/structuretoiceberg.py +355 -0
  127. avrotize/structuretojava/choice_core.jinja +34 -0
  128. avrotize/structuretojava/class_core.jinja +23 -0
  129. avrotize/structuretojava/enum_core.jinja +18 -0
  130. avrotize/structuretojava/equals_hashcode.jinja +30 -0
  131. avrotize/structuretojava/pom.xml.jinja +26 -0
  132. avrotize/structuretojava/tuple_core.jinja +49 -0
  133. avrotize/structuretojava.py +938 -0
  134. avrotize/structuretojs/class_core.js.jinja +33 -0
  135. avrotize/structuretojs/enum_core.js.jinja +10 -0
  136. avrotize/structuretojs/package.json.jinja +12 -0
  137. avrotize/structuretojs/test_class.js.jinja +84 -0
  138. avrotize/structuretojs/test_enum.js.jinja +58 -0
  139. avrotize/structuretojs/test_runner.js.jinja +45 -0
  140. avrotize/structuretojs.py +657 -0
  141. avrotize/structuretojsons.py +498 -0
  142. avrotize/structuretokusto.py +639 -0
  143. avrotize/structuretomd/README.md.jinja +204 -0
  144. avrotize/structuretomd.py +322 -0
  145. avrotize/structuretoproto.py +764 -0
  146. avrotize/structuretopython/dataclass_core.jinja +363 -0
  147. avrotize/structuretopython/enum_core.jinja +45 -0
  148. avrotize/structuretopython/map_alias.jinja +21 -0
  149. avrotize/structuretopython/pyproject_toml.jinja +23 -0
  150. avrotize/structuretopython/test_class.jinja +103 -0
  151. avrotize/structuretopython/test_enum.jinja +34 -0
  152. avrotize/structuretopython.py +799 -0
  153. avrotize/structuretorust/dataclass_enum.rs.jinja +63 -0
  154. avrotize/structuretorust/dataclass_struct.rs.jinja +121 -0
  155. avrotize/structuretorust/dataclass_union.rs.jinja +81 -0
  156. avrotize/structuretorust.py +714 -0
  157. avrotize/structuretots/class_core.ts.jinja +78 -0
  158. avrotize/structuretots/enum_core.ts.jinja +6 -0
  159. avrotize/structuretots/gitignore.jinja +8 -0
  160. avrotize/structuretots/index.ts.jinja +1 -0
  161. avrotize/structuretots/package.json.jinja +39 -0
  162. avrotize/structuretots/test_class.ts.jinja +35 -0
  163. avrotize/structuretots/tsconfig.json.jinja +21 -0
  164. avrotize/structuretots.py +740 -0
  165. avrotize/structuretoxsd.py +679 -0
  166. avrotize/xsdtoavro.py +413 -0
  167. avrotize-2.21.1.dist-info/METADATA +1319 -0
  168. avrotize-2.21.1.dist-info/RECORD +171 -0
  169. avrotize-2.21.1.dist-info/WHEEL +4 -0
  170. avrotize-2.21.1.dist-info/entry_points.txt +3 -0
  171. avrotize-2.21.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,697 @@
1
+ # pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-statements, line-too-long
2
+
3
+ """Generates C++ code from JSON Structure schema"""
4
+ import json
5
+ import os
6
+ from typing import Dict, List, Union, Set, Optional, Any, cast
7
+
8
+ from avrotize.common import pascal, process_template
9
+
10
+ INDENT = ' '
11
+
12
+ JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
13
+
14
+
15
+ class StructureToCpp:
16
+ """Converts JSON Structure schema to C++ code, including JSON serialization methods"""
17
+
18
+ def __init__(self, base_namespace: str = '') -> None:
19
+ self.base_namespace = base_namespace
20
+ self.output_dir = os.getcwd()
21
+ self.generated_types_namespace: Dict[str, str] = {}
22
+ self.generated_types_cpp_namespace: Dict[str, str] = {}
23
+ self.json_annotation = True # JSON Structure always has JSON serialization
24
+ self.generated_files: List[str] = []
25
+ self.test_files: List[str] = []
26
+ self.schema_doc: JsonNode = None
27
+ self.schema_registry: Dict[str, Dict] = {}
28
+ self.generated_structure_types: Dict[str, Dict[str, Union[str, Dict, List]]] = {}
29
+
30
+ def safe_identifier(self, name: str) -> str:
31
+ """Converts a name to a safe C++ identifier"""
32
+ reserved_words = [
33
+ 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'atomic_cancel', 'atomic_commit', 'atomic_noexcept', 'auto',
34
+ 'bitand', 'bitor', 'bool', 'break', 'case', 'catch', 'char', 'char8_t', 'char16_t', 'char32_t', 'class',
35
+ 'compl', 'concept', 'const', 'consteval', 'constexpr', 'constinit', 'const_cast', 'continue', 'co_await',
36
+ 'co_return', 'co_yield', 'decltype', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum',
37
+ 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long',
38
+ 'mutable', 'namespace', 'new', 'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'private',
39
+ 'protected', 'public', 'reflexpr', 'register', 'reinterpret_cast', 'requires', 'return', 'short', 'signed',
40
+ 'sizeof', 'static', 'static_assert', 'static_cast', 'struct', 'switch', 'synchronized', 'template', 'this',
41
+ 'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using',
42
+ 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'xor', 'xor_eq'
43
+ ]
44
+ if name in reserved_words:
45
+ return f"{name}_"
46
+ return name
47
+
48
+ def map_primitive_to_cpp(self, structure_type: str, is_optional: bool) -> str:
49
+ """Maps JSON Structure primitive types to C++ types"""
50
+ optional_mapping = {
51
+ 'null': 'std::optional<std::monostate>',
52
+ 'boolean': 'std::optional<bool>',
53
+ 'string': 'std::optional<std::string>',
54
+ 'integer': 'std::optional<int>',
55
+ 'number': 'std::optional<double>',
56
+ 'int8': 'std::optional<int8_t>',
57
+ 'uint8': 'std::optional<uint8_t>',
58
+ 'int16': 'std::optional<int16_t>',
59
+ 'uint16': 'std::optional<uint16_t>',
60
+ 'int32': 'std::optional<int32_t>',
61
+ 'uint32': 'std::optional<uint32_t>',
62
+ 'int64': 'std::optional<int64_t>',
63
+ 'uint64': 'std::optional<uint64_t>',
64
+ 'float': 'std::optional<float>',
65
+ 'double': 'std::optional<double>',
66
+ 'binary': 'std::optional<std::vector<uint8_t>>',
67
+ 'date': 'std::optional<std::chrono::system_clock::time_point>',
68
+ 'time': 'std::optional<std::chrono::milliseconds>',
69
+ 'datetime': 'std::optional<std::chrono::system_clock::time_point>',
70
+ 'timestamp': 'std::optional<std::chrono::system_clock::time_point>',
71
+ 'duration': 'std::optional<std::chrono::milliseconds>',
72
+ 'uuid': 'std::optional<boost::uuids::uuid>',
73
+ 'uri': 'std::optional<std::string>',
74
+ 'jsonpointer': 'std::optional<std::string>',
75
+ 'decimal': 'std::optional<std::string>',
76
+ 'any': 'nlohmann::json'
77
+ }
78
+ required_mapping = {
79
+ 'null': 'std::monostate',
80
+ 'boolean': 'bool',
81
+ 'string': 'std::string',
82
+ 'integer': 'int',
83
+ 'number': 'double',
84
+ 'int8': 'int8_t',
85
+ 'uint8': 'uint8_t',
86
+ 'int16': 'int16_t',
87
+ 'uint16': 'uint16_t',
88
+ 'int32': 'int32_t',
89
+ 'uint32': 'uint32_t',
90
+ 'int64': 'int64_t',
91
+ 'uint64': 'uint64_t',
92
+ 'float': 'float',
93
+ 'double': 'double',
94
+ 'binary': 'std::vector<uint8_t>',
95
+ 'date': 'std::chrono::system_clock::time_point',
96
+ 'time': 'std::chrono::milliseconds',
97
+ 'datetime': 'std::chrono::system_clock::time_point',
98
+ 'timestamp': 'std::chrono::system_clock::time_point',
99
+ 'duration': 'std::chrono::milliseconds',
100
+ 'uuid': 'boost::uuids::uuid',
101
+ 'uri': 'std::string',
102
+ 'jsonpointer': 'std::string',
103
+ 'decimal': 'std::string',
104
+ 'any': 'nlohmann::json'
105
+ }
106
+ if '.' in structure_type:
107
+ type_name = structure_type.split('.')[-1]
108
+ package_name = '::'.join(structure_type.split('.')[:-1]).lower()
109
+ structure_type = self.get_qualified_name(package_name, type_name)
110
+ if structure_type in self.generated_types_namespace:
111
+ return self.get_qualified_name(self.base_namespace, structure_type)
112
+ else:
113
+ return required_mapping.get(structure_type, structure_type) if not is_optional else optional_mapping.get(structure_type, f'std::optional<{required_mapping.get(structure_type, structure_type)}>')
114
+
115
+ def get_qualified_name(self, namespace: str, name: str) -> str:
116
+ """Concatenates namespace and name using a double colon separator"""
117
+ return f"{namespace}::{name}" if namespace else name
118
+
119
+ def concat_namespace(self, namespace: str, name: str) -> str:
120
+ """Concatenates namespace and name using a double colon separator"""
121
+ if namespace and name:
122
+ return f"{namespace}::{name}"
123
+ elif namespace:
124
+ return namespace
125
+ return name
126
+
127
+ def resolve_ref(self, ref: str, context_schema: Optional[Dict] = None) -> Optional[Dict]:
128
+ """ Resolves a $ref to the actual schema definition """
129
+ # Check if it's an absolute URI reference (schema with $id)
130
+ if not ref.startswith('#/'):
131
+ # Try to resolve from schema registry
132
+ if ref in self.schema_registry:
133
+ return self.schema_registry[ref]
134
+ return None
135
+
136
+ # Handle fragment-only references (internal to document)
137
+ path = ref[2:].split('/')
138
+ schema = context_schema if context_schema else self.schema_doc
139
+
140
+ for part in path:
141
+ if not isinstance(schema, dict) or part not in schema:
142
+ return None
143
+ schema = schema[part]
144
+
145
+ return schema
146
+
147
+ def register_schema_ids(self, schema: Dict, base_uri: str = '') -> None:
148
+ """ Recursively registers schemas with $id keywords """
149
+ if not isinstance(schema, dict):
150
+ return
151
+
152
+ # Register this schema if it has an $id
153
+ if '$id' in schema:
154
+ schema_id = schema['$id']
155
+ # Handle relative URIs
156
+ if base_uri and not schema_id.startswith(('http://', 'https://', 'urn:')):
157
+ from urllib.parse import urljoin
158
+ schema_id = urljoin(base_uri, schema_id)
159
+ self.schema_registry[schema_id] = schema
160
+ base_uri = schema_id # Update base URI for nested schemas
161
+
162
+ # Recursively process definitions
163
+ if 'definitions' in schema:
164
+ for def_name, def_schema in schema['definitions'].items():
165
+ if isinstance(def_schema, dict):
166
+ self.register_schema_ids(def_schema, base_uri)
167
+
168
+ # Recursively process properties
169
+ if 'properties' in schema:
170
+ for prop_name, prop_schema in schema['properties'].items():
171
+ if isinstance(prop_schema, dict):
172
+ self.register_schema_ids(prop_schema, base_uri)
173
+
174
+ # Recursively process items, values, etc.
175
+ for key in ['items', 'values', 'additionalProperties']:
176
+ if key in schema and isinstance(schema[key], dict):
177
+ self.register_schema_ids(schema[key], base_uri)
178
+
179
+ def convert_structure_type_to_cpp(self, class_name: str, field_name: str, structure_type: JsonNode, parent_namespace: str, nullable: bool = False) -> str:
180
+ """Converts JSON Structure type to C++ type"""
181
+ if isinstance(structure_type, str):
182
+ return self.map_primitive_to_cpp(structure_type, nullable)
183
+ elif isinstance(structure_type, list):
184
+ # Handle type unions
185
+ non_null_types = [t for t in structure_type if t != 'null']
186
+ is_nullable = 'null' in structure_type
187
+ if len(non_null_types) == 1:
188
+ if isinstance(non_null_types[0], str):
189
+ return self.map_primitive_to_cpp(non_null_types[0], is_nullable)
190
+ else:
191
+ cpp_type = self.convert_structure_type_to_cpp(class_name, field_name, non_null_types[0], parent_namespace, nullable=False)
192
+ if is_nullable:
193
+ return f'std::optional<{cpp_type}>'
194
+ return cpp_type
195
+ else:
196
+ types: List[str] = [self.convert_structure_type_to_cpp(class_name, field_name, t, parent_namespace, nullable=False) for t in non_null_types]
197
+ return 'std::variant<' + ', '.join(types) + '>'
198
+ elif isinstance(structure_type, dict):
199
+ # Handle $ref
200
+ if '$ref' in structure_type:
201
+ ref_schema = self.resolve_ref(structure_type['$ref'], self.schema_doc if isinstance(self.schema_doc, dict) else None)
202
+ if ref_schema:
203
+ ref_path = structure_type['$ref'].split('/')
204
+ type_name = ref_path[-1]
205
+ ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
206
+ return self.generate_class_or_choice(ref_schema, ref_namespace, write_file=True, explicit_name=type_name)
207
+ return 'nlohmann::json'
208
+
209
+ # Handle enum keyword
210
+ if 'enum' in structure_type:
211
+ return self.generate_enum(structure_type, field_name, parent_namespace, write_file=True)
212
+
213
+ # Handle type keyword
214
+ if 'type' not in structure_type:
215
+ return 'nlohmann::json'
216
+
217
+ struct_type = structure_type['type']
218
+
219
+ if struct_type == 'object':
220
+ return self.generate_class(structure_type, parent_namespace, write_file=True)
221
+ elif struct_type == 'array':
222
+ items_type = self.convert_structure_type_to_cpp(class_name, field_name+'Item', structure_type.get('items', {'type': 'any'}), parent_namespace, nullable=False)
223
+ return f"std::vector<{items_type}>"
224
+ elif struct_type == 'set':
225
+ items_type = self.convert_structure_type_to_cpp(class_name, field_name+'Item', structure_type.get('items', {'type': 'any'}), parent_namespace, nullable=False)
226
+ return f"std::set<{items_type}>"
227
+ elif struct_type == 'map':
228
+ values_type = self.convert_structure_type_to_cpp(class_name, field_name+'Value', structure_type.get('values', {'type': 'any'}), parent_namespace, nullable=False)
229
+ return f"std::map<std::string, {values_type}>"
230
+ elif struct_type == 'choice':
231
+ return self.generate_choice(structure_type, parent_namespace, write_file=True)
232
+ elif struct_type == 'tuple':
233
+ return self.generate_tuple(structure_type, parent_namespace, write_file=True)
234
+ else:
235
+ return self.convert_structure_type_to_cpp(class_name, field_name, struct_type, parent_namespace, nullable)
236
+ return 'nlohmann::json'
237
+
238
+ def generate_class_or_choice(self, structure_schema: Dict, parent_namespace: str, write_file: bool = True, explicit_name: str = '') -> str:
239
+ """Generates a Class or Choice"""
240
+ struct_type = structure_schema.get('type', 'object')
241
+ if struct_type == 'object':
242
+ return self.generate_class(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
243
+ elif struct_type == 'choice':
244
+ return self.generate_choice(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
245
+ elif struct_type == 'tuple':
246
+ return self.generate_tuple(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
247
+ elif struct_type in ('map', 'array', 'set'):
248
+ # For root-level container types, generate a type alias
249
+ return self.generate_container_alias(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
250
+ return 'nlohmann::json'
251
+
252
+ def generate_class(self, structure_schema: Dict, parent_namespace: str, write_file: bool, explicit_name: str = '') -> str:
253
+ """Generates a C++ class from a JSON Structure object type"""
254
+ class_definition = ''
255
+
256
+ # Get name and namespace
257
+ class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedClass'))
258
+ schema_namespace = structure_schema.get('namespace', parent_namespace)
259
+ namespace = self.concat_namespace(self.base_namespace, schema_namespace.replace('.', '::'))
260
+
261
+ qualified_class_name = self.get_qualified_name(namespace, class_name)
262
+ if qualified_class_name in self.generated_types_namespace:
263
+ return qualified_class_name
264
+
265
+ self.generated_types_namespace[qualified_class_name] = schema_namespace
266
+ self.generated_types_cpp_namespace[qualified_class_name] = "class"
267
+ self.generated_structure_types[qualified_class_name] = structure_schema
268
+
269
+ # Track the includes for member types
270
+ member_includes = set()
271
+
272
+ # Generate class documentation
273
+ if 'description' in structure_schema or 'doc' in structure_schema:
274
+ doc = structure_schema.get('description', structure_schema.get('doc', ''))
275
+ class_definition += f"// {doc}\n"
276
+
277
+ # Check if this is an abstract type
278
+ is_abstract = structure_schema.get('abstract', False)
279
+
280
+ class_definition += f"class {class_name} {{\n"
281
+ class_definition += "public:\n"
282
+
283
+ # Generate properties
284
+ properties = structure_schema.get('properties', {})
285
+ required_props = structure_schema.get('required', [])
286
+
287
+ for prop_name, prop_schema in properties.items():
288
+ field_name = self.safe_identifier(prop_name)
289
+ is_required = prop_name in required_props if not isinstance(required_props, list) or len(required_props) == 0 or not isinstance(required_props[0], list) else any(prop_name in req_set for req_set in required_props)
290
+
291
+ # Convert to C++ type
292
+ field_type = self.convert_structure_type_to_cpp(class_name, field_name, prop_schema, schema_namespace, nullable=not is_required)
293
+
294
+ # Add documentation
295
+ if 'description' in prop_schema or 'doc' in prop_schema:
296
+ field_doc = prop_schema.get('description', prop_schema.get('doc', ''))
297
+ class_definition += f"{INDENT}// {field_doc}\n"
298
+
299
+ class_definition += f"{INDENT}{field_type} {field_name};\n"
300
+
301
+ # Add default constructor
302
+ class_definition += f"\npublic:\n"
303
+ class_definition += f"{INDENT}{class_name}() = default;\n"
304
+
305
+ # Add JSON serialization methods
306
+ class_definition += process_template("structuretocpp/dataclass_body.jinja",
307
+ class_name=class_name,
308
+ json_annotation=self.json_annotation)
309
+
310
+ if self.json_annotation:
311
+ class_definition += self.generate_to_json_method(class_name)
312
+
313
+ class_definition += "};\n\n"
314
+
315
+ # Create includes
316
+ includes = self.generate_includes(member_includes)
317
+
318
+ if write_file:
319
+ self.write_to_file(namespace, class_name, includes, class_definition)
320
+ self.generate_unit_test(class_name, properties, namespace, required_props)
321
+
322
+ return qualified_class_name
323
+
324
+ def generate_enum(self, structure_schema: Dict, field_name: str, parent_namespace: str, write_file: bool) -> str:
325
+ """Generates a C++ enum from a JSON Structure enum"""
326
+ enum_definition = ''
327
+
328
+ # Generate enum name
329
+ enum_name = pascal(structure_schema.get('name', field_name + 'Enum'))
330
+ schema_namespace = structure_schema.get('namespace', parent_namespace)
331
+ namespace = self.concat_namespace(self.base_namespace, schema_namespace.replace('.', '::'))
332
+
333
+ qualified_enum_name = self.get_qualified_name(namespace, enum_name)
334
+ if qualified_enum_name in self.generated_types_namespace:
335
+ return qualified_enum_name
336
+
337
+ self.generated_types_namespace[qualified_enum_name] = schema_namespace
338
+ self.generated_types_cpp_namespace[qualified_enum_name] = "enum"
339
+
340
+ if 'description' in structure_schema or 'doc' in structure_schema:
341
+ doc = structure_schema.get('description', structure_schema.get('doc', ''))
342
+ enum_definition += f"// {doc}\n"
343
+
344
+ symbols = structure_schema.get('enum', [])
345
+ enum_definition += f"enum class {enum_name} {{\n"
346
+ for symbol in symbols:
347
+ enum_definition += f"{INDENT}{self.safe_identifier(str(symbol))},\n"
348
+ enum_definition += "};\n\n"
349
+
350
+ if write_file:
351
+ self.write_to_file(namespace, enum_name, "", enum_definition)
352
+
353
+ return qualified_enum_name
354
+
355
+ def generate_choice(self, structure_schema: Dict, parent_namespace: str, write_file: bool, explicit_name: str = '') -> str:
356
+ """Generates a choice (discriminated union) type"""
357
+ # For now, return variant of the choice types
358
+ choices = structure_schema.get('choices', {})
359
+ choice_types = []
360
+
361
+ for choice_name, choice_schema in choices.items():
362
+ if isinstance(choice_schema, dict):
363
+ if '$ref' in choice_schema:
364
+ ref_schema = self.resolve_ref(choice_schema['$ref'], self.schema_doc if isinstance(self.schema_doc, dict) else None)
365
+ if ref_schema:
366
+ ref_path = choice_schema['$ref'].split('/')
367
+ type_name = ref_path[-1]
368
+ ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
369
+ qualified_name = self.generate_class(ref_schema, ref_namespace, write_file=True, explicit_name=type_name)
370
+ choice_types.append(qualified_name)
371
+ elif 'type' in choice_schema:
372
+ choice_type = self.convert_structure_type_to_cpp('Choice', choice_name, choice_schema, parent_namespace, nullable=False)
373
+ choice_types.append(choice_type)
374
+
375
+ if len(choice_types) == 0:
376
+ return 'nlohmann::json'
377
+ elif len(choice_types) == 1:
378
+ return choice_types[0]
379
+ else:
380
+ return f"std::variant<{', '.join(choice_types)}>"
381
+
382
+ def generate_tuple(self, structure_schema: Dict, parent_namespace: str, write_file: bool, explicit_name: str = '') -> str:
383
+ """Generates a tuple type"""
384
+ # Tuples serialize as JSON arrays
385
+ # For now, use std::tuple
386
+ properties = structure_schema.get('properties', {})
387
+ tuple_order = structure_schema.get('tuple', [])
388
+
389
+ tuple_types = []
390
+ for prop_name in tuple_order:
391
+ if prop_name in properties:
392
+ prop_schema = properties[prop_name]
393
+ prop_type = self.convert_structure_type_to_cpp('Tuple', prop_name, prop_schema, parent_namespace, nullable=False)
394
+ tuple_types.append(prop_type)
395
+
396
+ if len(tuple_types) == 0:
397
+ return 'nlohmann::json'
398
+
399
+ return f"std::tuple<{', '.join(tuple_types)}>"
400
+
401
+ def generate_container_alias(self, structure_schema: Dict, parent_namespace: str, write_file: bool, explicit_name: str = '') -> str:
402
+ """Generates a type alias for root-level container types (map, array, set)"""
403
+ struct_type = structure_schema.get('type', 'map')
404
+
405
+ # Get name and namespace
406
+ class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', f'Root{struct_type.capitalize()}'))
407
+ schema_namespace = structure_schema.get('namespace', parent_namespace)
408
+ namespace = self.concat_namespace(self.base_namespace, schema_namespace.replace('.', '::'))
409
+
410
+ qualified_name = self.get_qualified_name(namespace, class_name)
411
+ if qualified_name in self.generated_types_namespace:
412
+ return qualified_name
413
+
414
+ self.generated_types_namespace[qualified_name] = schema_namespace
415
+ self.generated_types_cpp_namespace[qualified_name] = "alias"
416
+ self.generated_structure_types[qualified_name] = structure_schema
417
+
418
+ # Determine the underlying type
419
+ if struct_type == 'map':
420
+ values_type = self.convert_structure_type_to_cpp(class_name, 'Value', structure_schema.get('values', {'type': 'any'}), schema_namespace, nullable=False)
421
+ underlying_type = f"std::map<std::string, {values_type}>"
422
+ elif struct_type == 'array':
423
+ items_type = self.convert_structure_type_to_cpp(class_name, 'Item', structure_schema.get('items', {'type': 'any'}), schema_namespace, nullable=False)
424
+ underlying_type = f"std::vector<{items_type}>"
425
+ elif struct_type == 'set':
426
+ items_type = self.convert_structure_type_to_cpp(class_name, 'Item', structure_schema.get('items', {'type': 'any'}), schema_namespace, nullable=False)
427
+ underlying_type = f"std::set<{items_type}>"
428
+ else:
429
+ underlying_type = 'nlohmann::json'
430
+
431
+ # Generate type alias
432
+ alias_definition = ''
433
+ if 'description' in structure_schema or 'doc' in structure_schema:
434
+ doc = structure_schema.get('description', structure_schema.get('doc', ''))
435
+ alias_definition += f"// {doc}\n"
436
+
437
+ alias_definition += f"using {class_name} = {underlying_type};\n\n"
438
+
439
+ if write_file:
440
+ self.write_to_file(namespace, class_name, "", alias_definition)
441
+
442
+ return qualified_name
443
+
444
+ def generate_to_json_method(self, class_name: str) -> str:
445
+ """Generates the to_json method for the class"""
446
+ method_definition = f"\nnlohmann::json to_json() const {{\n"
447
+ method_definition += f"{INDENT}return nlohmann::json(*this);\n"
448
+ method_definition += f"}}\n"
449
+ return method_definition
450
+
451
+ def generate_includes(self, member_includes: set) -> str:
452
+ """Generates the include statements for the member types"""
453
+ includes = '\n'.join([f'#include "{include.replace("::", "/")}.hpp"' for include in member_includes])
454
+ return includes
455
+
456
+ def generate_unit_test(self, class_name: str, properties: Dict, namespace: str, required_props: List) -> None:
457
+ """Generates a unit test for a given class"""
458
+ test_definition = f'#include <gtest/gtest.h>\n'
459
+ test_definition += f'#include "{namespace.replace("::", "/")}/{class_name}.hpp"\n\n'
460
+ test_definition += f'TEST({class_name}Test, PropertiesTest) {{\n'
461
+ test_definition += f'{INDENT}{namespace}::{class_name} instance;\n'
462
+
463
+ for prop_name, prop_schema in properties.items():
464
+ field_name = self.safe_identifier(prop_name)
465
+ is_required = prop_name in required_props if not isinstance(required_props, list) or len(required_props) == 0 or not isinstance(required_props[0], list) else any(prop_name in req_set for req_set in required_props)
466
+ test_value = self.get_test_value(prop_schema, is_required)
467
+ test_definition += f'{INDENT}instance.{field_name} = {test_value};\n'
468
+ test_definition += f'{INDENT}EXPECT_EQ(instance.{field_name}, {test_value});\n'
469
+
470
+ test_definition += '}\n'
471
+
472
+ test_dir = os.path.join(self.output_dir, "tests")
473
+ if not os.path.exists(test_dir):
474
+ os.makedirs(test_dir, exist_ok=True)
475
+
476
+ test_file_path = os.path.join(test_dir, f"{class_name}_test.cpp")
477
+ with open(test_file_path, 'w', encoding='utf-8') as file:
478
+ file.write(test_definition)
479
+
480
+ self.test_files.append(test_file_path.replace(os.sep, '/'))
481
+
482
+ def get_test_value(self, structure_schema: JsonNode, is_required: bool) -> str:
483
+ """Returns a default test value based on the Structure type"""
484
+ if isinstance(structure_schema, str):
485
+ test_values = {
486
+ 'string': '"test_string"',
487
+ 'boolean': 'true',
488
+ 'integer': '42',
489
+ 'number': '3.14',
490
+ 'int8': '42',
491
+ 'uint8': '42',
492
+ 'int16': '42',
493
+ 'uint16': '42',
494
+ 'int32': '42',
495
+ 'uint32': '42',
496
+ 'int64': '42LL',
497
+ 'uint64': '42ULL',
498
+ 'float': '3.14f',
499
+ 'double': '3.14',
500
+ 'binary': 'std::vector<uint8_t>{0x01, 0x02, 0x03}',
501
+ 'date': 'std::chrono::system_clock::now()',
502
+ 'time': 'std::chrono::milliseconds(123456)',
503
+ 'datetime': 'std::chrono::system_clock::now()',
504
+ 'timestamp': 'std::chrono::system_clock::now()',
505
+ 'duration': 'std::chrono::milliseconds(1000)',
506
+ 'uuid': 'boost::uuids::random_generator()()',
507
+ 'uri': '"https://example.com"',
508
+ 'jsonpointer': '"/path/to/field"',
509
+ 'decimal': '"123.45"',
510
+ 'any': 'nlohmann::json::object()',
511
+ 'null': 'std::monostate()',
512
+ }
513
+ return test_values.get(structure_schema, '"test"')
514
+
515
+ elif isinstance(structure_schema, list):
516
+ # For unions, use the first non-null type
517
+ non_null_types = [t for t in structure_schema if t != 'null']
518
+ if non_null_types:
519
+ return self.get_test_value(non_null_types[0], True)
520
+ return 'std::monostate()'
521
+
522
+ elif isinstance(structure_schema, dict):
523
+ struct_type = structure_schema.get('type', 'any')
524
+
525
+ if 'enum' in structure_schema:
526
+ symbols = structure_schema.get('enum', [])
527
+ if symbols:
528
+ return f"{pascal(structure_schema.get('name', 'Enum'))}_::{self.safe_identifier(str(symbols[0]))}"
529
+ return '"test"'
530
+
531
+ if struct_type == 'object':
532
+ return f"{pascal(structure_schema.get('name', 'Object'))}()"
533
+ elif struct_type == 'array':
534
+ item_type = self.convert_structure_type_to_cpp('Test', 'Item', structure_schema.get('items', {'type': 'any'}), '', False)
535
+ return f"std::vector<{item_type}>{{}}"
536
+ elif struct_type == 'set':
537
+ item_type = self.convert_structure_type_to_cpp('Test', 'Item', structure_schema.get('items', {'type': 'any'}), '', False)
538
+ return f"std::set<{item_type}>{{}}"
539
+ elif struct_type == 'map':
540
+ value_type = self.convert_structure_type_to_cpp('Test', 'Value', structure_schema.get('values', {'type': 'any'}), '', False)
541
+ return f"std::map<std::string, {value_type}>{{}}"
542
+ elif struct_type == 'tuple':
543
+ return '"tuple_test"'
544
+ else:
545
+ return self.get_test_value(struct_type, is_required)
546
+
547
+ return '"test"'
548
+
549
+ def write_to_file(self, namespace: str, name: str, includes: str, definition: str) -> None:
550
+ """Writes a C++ class or enum to a file"""
551
+ directory_path = os.path.join(
552
+ self.output_dir, "include", namespace.replace('::', os.sep))
553
+ if not os.path.exists(directory_path):
554
+ os.makedirs(directory_path, exist_ok=True)
555
+ file_path = os.path.join(directory_path, f"{name}.hpp")
556
+
557
+ with open(file_path, 'w', encoding='utf-8') as file:
558
+ file.write("#pragma once\n")
559
+ if self.json_annotation:
560
+ file.write("#include <nlohmann/json.hpp>\n")
561
+ if "std::vector" in definition:
562
+ file.write("#include <vector>\n")
563
+ if "std::set" in definition:
564
+ file.write("#include <set>\n")
565
+ if "std::map" in definition:
566
+ file.write("#include <map>\n")
567
+ if "std::tuple" in definition:
568
+ file.write("#include <tuple>\n")
569
+ if "std::variant" in definition:
570
+ file.write("#include <variant>\n")
571
+ if "std::optional" in definition:
572
+ file.write("#include <optional>\n")
573
+ file.write("#include <stdexcept>\n")
574
+ if "std::chrono" in definition:
575
+ file.write("#include <chrono>\n")
576
+ if "boost::uuid" in definition:
577
+ file.write("#include <boost/uuid/uuid.hpp>\n")
578
+ file.write("#include <boost/uuid/uuid_io.hpp>\n")
579
+ file.write("#include <boost/uuid/uuid_generators.hpp>\n")
580
+ if includes:
581
+ file.write(includes + '\n')
582
+ if namespace:
583
+ file.write(f"\nnamespace {namespace} {{\n\n")
584
+ file.write(definition)
585
+ if namespace:
586
+ file.write(f"}} // namespace {namespace}\n")
587
+
588
+ # Collect the generated file names
589
+ self.generated_files.append(file_path.replace(os.sep, '/'))
590
+
591
+ def generate_cmake_lists(self, project_name: str) -> None:
592
+ """Generates a CMakeLists.txt file"""
593
+ cmake_content = process_template("structuretocpp/CMakeLists.txt.jinja",
594
+ project_name=project_name,
595
+ json_annotation=self.json_annotation)
596
+ cmake_path = os.path.join(self.output_dir, 'CMakeLists.txt')
597
+ with open(cmake_path, 'w', encoding='utf-8') as file:
598
+ file.write(cmake_content)
599
+
600
+ def generate_vcpkg_json(self) -> None:
601
+ """Generates a vcpkg.json file"""
602
+ vcpkg_json = process_template("structuretocpp/vcpkg.json.jinja",
603
+ project_name=self.base_namespace,
604
+ json_annotation=self.json_annotation)
605
+ vcpkg_json_path = os.path.join(self.output_dir, 'vcpkg.json')
606
+ with open(vcpkg_json_path, 'w', encoding='utf-8') as file:
607
+ file.write(vcpkg_json)
608
+
609
+ def generate_build_scripts(self) -> None:
610
+ """Generates build scripts for Windows and Linux"""
611
+ build_script_linux = process_template("structuretocpp/build.sh.jinja")
612
+ build_script_windows = process_template("structuretocpp/build.bat.jinja")
613
+ script_path_linux = os.path.join(self.output_dir, 'build.sh')
614
+ script_path_windows = os.path.join(self.output_dir, 'build.bat')
615
+
616
+ with open(script_path_linux, 'w', encoding='utf-8') as file:
617
+ file.write(build_script_linux)
618
+
619
+ with open(script_path_windows, 'w', encoding='utf-8') as file:
620
+ file.write(build_script_windows)
621
+
622
+ def convert_schema(self, schema: Union[Dict, List], output_dir: str) -> None:
623
+ """Converts JSON Structure schema to C++"""
624
+ if not isinstance(schema, list):
625
+ schema = [schema]
626
+ if not os.path.exists(output_dir):
627
+ os.makedirs(output_dir, exist_ok=True)
628
+ self.output_dir = output_dir
629
+
630
+ # Register all schema IDs first
631
+ for structure_schema in (s for s in schema if isinstance(s, dict)):
632
+ self.register_schema_ids(structure_schema)
633
+
634
+ # Process each schema
635
+ for structure_schema in (s for s in schema if isinstance(s, dict)):
636
+ self.schema_doc = structure_schema
637
+
638
+ # Process root type
639
+ if 'type' in structure_schema:
640
+ self.generate_class_or_choice(structure_schema, '', write_file=True)
641
+ elif '$root' in structure_schema:
642
+ root_ref = structure_schema['$root']
643
+ root_schema = self.resolve_ref(root_ref, structure_schema)
644
+ if root_schema:
645
+ ref_path = root_ref.split('/')
646
+ type_name = ref_path[-1]
647
+ ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else ''
648
+ self.generate_class_or_choice(root_schema, ref_namespace, write_file=True, explicit_name=type_name)
649
+
650
+ # Process definitions
651
+ if 'definitions' in structure_schema:
652
+ self.process_definitions(structure_schema['definitions'], '')
653
+
654
+ self.generate_cmake_lists(self.base_namespace)
655
+ self.generate_build_scripts()
656
+ self.generate_vcpkg_json()
657
+
658
+ def process_definitions(self, definitions: Dict, namespace_path: str) -> None:
659
+ """ Processes the definitions section recursively """
660
+ for name, definition in definitions.items():
661
+ if isinstance(definition, dict):
662
+ if 'type' in definition or 'enum' in definition:
663
+ # This is a type definition
664
+ current_namespace = self.concat_namespace(namespace_path, '')
665
+ # Check if this type was already generated
666
+ check_namespace = self.concat_namespace(self.base_namespace, current_namespace).replace('.', '::')
667
+ check_name = pascal(name)
668
+ check_ref = self.get_qualified_name(check_namespace, check_name)
669
+ if check_ref not in self.generated_types_namespace:
670
+ self.generate_class_or_choice(definition, current_namespace, write_file=True, explicit_name=name)
671
+ else:
672
+ # This is a namespace
673
+ new_namespace = self.concat_namespace(namespace_path, name)
674
+ self.process_definitions(definition, new_namespace)
675
+
676
+ def convert(self, structure_schema_path: str, output_dir: str) -> None:
677
+ """Converts JSON Structure schema to C++"""
678
+ with open(structure_schema_path, 'r', encoding='utf-8') as file:
679
+ schema = json.load(file)
680
+ self.convert_schema(schema, output_dir)
681
+
682
+
683
+ def convert_structure_to_cpp(structure_schema_path: str, output_dir: str, namespace: str = '', json_annotation: bool = True) -> None:
684
+ """Converts JSON Structure schema to C++ classes"""
685
+
686
+ if not namespace:
687
+ namespace = os.path.splitext(os.path.basename(structure_schema_path))[0].replace('-', '_')
688
+
689
+ structure_to_cpp = StructureToCpp(namespace)
690
+ structure_to_cpp.json_annotation = json_annotation
691
+ structure_to_cpp.convert(structure_schema_path, output_dir)
692
+
693
+ def convert_structure_schema_to_cpp(structure_schema: Dict, output_dir: str, namespace: str = '', json_annotation: bool = True) -> None:
694
+ """Converts JSON Structure schema to C++ classes"""
695
+ structure_to_cpp = StructureToCpp(namespace)
696
+ structure_to_cpp.json_annotation = json_annotation
697
+ structure_to_cpp.convert_schema(structure_schema, output_dir)