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,729 @@
1
+ # pylint: disable=line-too-long
2
+
3
+ """ StructureToGo class for converting JSON Structure schema to Go structs """
4
+
5
+ import json
6
+ import os
7
+ from typing import Any, Dict, List, Set, Union, Optional, cast
8
+
9
+ from avrotize.common import pascal, render_template
10
+
11
+ JsonNode = Dict[str, 'JsonNode'] | List['JsonNode'] | str | None
12
+
13
+ INDENT = ' '
14
+
15
+
16
+ class StructureToGo:
17
+ """ Converts JSON Structure schema to Go structs """
18
+
19
+ def __init__(self, base_package: str = '') -> None:
20
+ self.base_package = base_package
21
+ self.output_dir = os.getcwd()
22
+ self.json_annotation = False
23
+ self.avro_annotation = False
24
+ self.package_site = 'github.com'
25
+ self.package_username = 'username'
26
+ self.schema_doc: JsonNode = None
27
+ self.generated_types: Dict[str, str] = {}
28
+ self.generated_structure_types: Dict[str, Dict[str, Union[str, Dict, List]]] = {}
29
+ self.definitions: Dict[str, Any] = {}
30
+ self.schema_registry: Dict[str, Dict] = {}
31
+ self.structs: List[Dict] = []
32
+ self.enums: List[Dict] = []
33
+
34
+ def safe_identifier(self, name: str) -> str:
35
+ """Converts a name to a safe Go identifier"""
36
+ reserved_words = [
37
+ 'break', 'default', 'func', 'interface', 'select', 'case', 'defer', 'go', 'map', 'struct', 'chan',
38
+ 'else', 'goto', 'package', 'switch', 'const', 'fallthrough', 'if', 'range', 'type', 'continue', 'for',
39
+ 'import', 'return', 'var',
40
+ ]
41
+ if name in reserved_words:
42
+ return f"{name}_"
43
+ return name
44
+
45
+ def go_type_name(self, name: str, namespace: str = '') -> str:
46
+ """Returns a qualified name for a Go struct or enum"""
47
+ if namespace:
48
+ namespace = ''.join([pascal(part) for part in namespace.split('.')])
49
+ return f"{namespace}{pascal(name)}"
50
+ return pascal(name)
51
+
52
+ def map_primitive_to_go(self, structure_type: str, is_optional: bool) -> str:
53
+ """Maps JSON Structure primitive types to Go types"""
54
+ optional_mapping = {
55
+ 'null': 'interface{}',
56
+ 'boolean': '*bool',
57
+ 'string': '*string',
58
+ 'integer': '*int',
59
+ 'number': '*float64',
60
+ 'int8': '*int8',
61
+ 'uint8': '*uint8',
62
+ 'int16': '*int16',
63
+ 'uint16': '*uint16',
64
+ 'int32': '*int32',
65
+ 'uint32': '*uint32',
66
+ 'int64': '*int64',
67
+ 'uint64': '*uint64',
68
+ 'int128': '*big.Int',
69
+ 'uint128': '*big.Int',
70
+ 'float8': '*float32',
71
+ 'float': '*float32',
72
+ 'double': '*float64',
73
+ 'binary32': '*float32',
74
+ 'binary64': '*float64',
75
+ 'decimal': '*float64',
76
+ 'binary': '[]byte',
77
+ 'date': '*time.Time',
78
+ 'time': '*time.Time',
79
+ 'datetime': '*time.Time',
80
+ 'timestamp': '*time.Time',
81
+ 'duration': '*time.Duration',
82
+ 'uuid': '*string',
83
+ 'uri': '*string',
84
+ 'jsonpointer': '*string',
85
+ 'any': 'interface{}',
86
+ }
87
+ required_mapping = {
88
+ 'null': 'interface{}',
89
+ 'boolean': 'bool',
90
+ 'string': 'string',
91
+ 'integer': 'int',
92
+ 'number': 'float64',
93
+ 'int8': 'int8',
94
+ 'uint8': 'uint8',
95
+ 'int16': 'int16',
96
+ 'uint16': 'uint16',
97
+ 'int32': 'int32',
98
+ 'uint32': 'uint32',
99
+ 'int64': 'int64',
100
+ 'uint64': 'uint64',
101
+ 'int128': 'big.Int',
102
+ 'uint128': 'big.Int',
103
+ 'float8': 'float32',
104
+ 'float': 'float32',
105
+ 'double': 'float64',
106
+ 'binary32': 'float32',
107
+ 'binary64': 'float64',
108
+ 'decimal': 'float64',
109
+ 'binary': '[]byte',
110
+ 'date': 'time.Time',
111
+ 'time': 'time.Time',
112
+ 'datetime': 'time.Time',
113
+ 'timestamp': 'time.Time',
114
+ 'duration': 'time.Duration',
115
+ 'uuid': 'string',
116
+ 'uri': 'string',
117
+ 'jsonpointer': 'string',
118
+ 'any': 'interface{}',
119
+ }
120
+ if structure_type in self.generated_types:
121
+ return structure_type
122
+ else:
123
+ return required_mapping.get(structure_type, 'interface{}') if not is_optional else optional_mapping.get(structure_type, 'interface{}')
124
+
125
+ def resolve_ref(self, ref: str, context_schema: Optional[Dict] = None) -> Optional[Dict]:
126
+ """ Resolves a $ref to the actual schema definition """
127
+ # Check if it's an absolute URI reference
128
+ if not ref.startswith('#/'):
129
+ if ref in self.schema_registry:
130
+ return self.schema_registry[ref]
131
+ return None
132
+
133
+ # Handle fragment-only references
134
+ path = ref[2:].split('/')
135
+ schema = context_schema if context_schema else self.schema_doc
136
+
137
+ for part in path:
138
+ if not isinstance(schema, dict) or part not in schema:
139
+ return None
140
+ schema = schema[part]
141
+
142
+ return schema
143
+
144
+ def register_schema_ids(self, schema: Dict, base_uri: str = '') -> None:
145
+ """ Recursively registers schemas with $id keywords """
146
+ if not isinstance(schema, dict):
147
+ return
148
+
149
+ if '$id' in schema:
150
+ schema_id = schema['$id']
151
+ if base_uri and not schema_id.startswith(('http://', 'https://', 'urn:')):
152
+ from urllib.parse import urljoin
153
+ schema_id = urljoin(base_uri, schema_id)
154
+ self.schema_registry[schema_id] = schema
155
+ base_uri = schema_id
156
+
157
+ if 'definitions' in schema:
158
+ for def_name, def_schema in schema['definitions'].items():
159
+ if isinstance(def_schema, dict):
160
+ self.register_schema_ids(def_schema, base_uri)
161
+
162
+ if 'properties' in schema:
163
+ for prop_name, prop_schema in schema['properties'].items():
164
+ if isinstance(prop_schema, dict):
165
+ self.register_schema_ids(prop_schema, base_uri)
166
+
167
+ for key in ['items', 'values', 'additionalProperties']:
168
+ if key in schema and isinstance(schema[key], dict):
169
+ self.register_schema_ids(schema[key], base_uri)
170
+
171
+ def convert_structure_type_to_go(self, class_name: str, field_name: str,
172
+ structure_type: JsonNode, parent_namespace: str,
173
+ nullable: bool = False) -> str:
174
+ """ Converts JSON Structure type to Go type """
175
+ if isinstance(structure_type, str):
176
+ return self.map_primitive_to_go(structure_type, nullable)
177
+ elif isinstance(structure_type, list):
178
+ # Handle type unions
179
+ non_null_types = [t for t in structure_type if t != 'null']
180
+ has_null = 'null' in structure_type
181
+ if len(non_null_types) == 1:
182
+ inner_type = self.convert_structure_type_to_go(
183
+ class_name, field_name, non_null_types[0], parent_namespace, has_null)
184
+ return inner_type
185
+ else:
186
+ # Generate union type
187
+ return self.generate_union_class(field_name, structure_type, parent_namespace)
188
+ elif isinstance(structure_type, dict):
189
+ # Handle $ref
190
+ if '$ref' in structure_type:
191
+ ref_schema = self.resolve_ref(structure_type['$ref'], self.schema_doc)
192
+ if ref_schema:
193
+ ref_path = structure_type['$ref'].split('/')
194
+ type_name = ref_path[-1]
195
+ ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
196
+ return self.generate_class_or_choice(ref_schema, ref_namespace, write_file=True, explicit_name=type_name)
197
+ return 'interface{}'
198
+
199
+ # Handle enum keyword
200
+ if 'enum' in structure_type:
201
+ return self.generate_enum(structure_type, field_name, parent_namespace, write_file=True)
202
+
203
+ # Handle type keyword
204
+ if 'type' not in structure_type:
205
+ return 'interface{}'
206
+
207
+ struct_type = structure_type['type']
208
+
209
+ # Handle complex types
210
+ if struct_type == 'object':
211
+ return self.generate_class(structure_type, parent_namespace, write_file=True)
212
+ elif struct_type == 'array':
213
+ items_type = self.convert_structure_type_to_go(
214
+ class_name, field_name+'List', structure_type.get('items', {'type': 'any'}),
215
+ parent_namespace, nullable=True)
216
+ if items_type.startswith('*'):
217
+ return f"[]{items_type[1:]}"
218
+ return f"[]{items_type}"
219
+ elif struct_type == 'set':
220
+ # Go doesn't have a built-in set, use map[T]bool
221
+ items_type = self.convert_structure_type_to_go(
222
+ class_name, field_name+'Set', structure_type.get('items', {'type': 'any'}),
223
+ parent_namespace, nullable=True)
224
+ if items_type.startswith('*'):
225
+ items_type = items_type[1:]
226
+ return f"map[{items_type}]bool"
227
+ elif struct_type == 'map':
228
+ values_type = self.convert_structure_type_to_go(
229
+ class_name, field_name+'Map', structure_type.get('values', {'type': 'any'}),
230
+ parent_namespace, nullable=True)
231
+ return f"map[string]{values_type}"
232
+ elif struct_type == 'choice':
233
+ return self.generate_choice(structure_type, parent_namespace, write_file=True)
234
+ elif struct_type == 'tuple':
235
+ return self.generate_tuple(structure_type, parent_namespace, write_file=True)
236
+ else:
237
+ return self.convert_structure_type_to_go(class_name, field_name, struct_type, parent_namespace, nullable)
238
+ return 'interface{}'
239
+
240
+ def generate_class_or_choice(self, structure_schema: Dict, parent_namespace: str,
241
+ write_file: bool = True, explicit_name: str = '') -> str:
242
+ """ Generates a Class or Choice """
243
+ struct_type = structure_schema.get('type', 'object')
244
+ if struct_type == 'object':
245
+ return self.generate_class(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
246
+ elif struct_type == 'choice':
247
+ return self.generate_choice(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
248
+ elif struct_type == 'tuple':
249
+ return self.generate_tuple(structure_schema, parent_namespace, write_file, explicit_name=explicit_name)
250
+ return 'interface{}'
251
+
252
+ def generate_class(self, structure_schema: Dict, parent_namespace: str,
253
+ write_file: bool, explicit_name: str = '') -> str:
254
+ """ Generates a Go struct from JSON Structure object type """
255
+ class_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedClass'))
256
+ schema_namespace = structure_schema.get('namespace', parent_namespace)
257
+ go_struct_name = self.go_type_name(class_name, schema_namespace)
258
+
259
+ if go_struct_name in self.generated_types:
260
+ return go_struct_name
261
+
262
+ # Check if this is an abstract type
263
+ is_abstract = structure_schema.get('abstract', False)
264
+
265
+ # If abstract, generate interface instead
266
+ if is_abstract:
267
+ return self.generate_interface(structure_schema, parent_namespace, write_file, explicit_name)
268
+
269
+ self.generated_types[go_struct_name] = "struct"
270
+ self.generated_structure_types[go_struct_name] = structure_schema
271
+
272
+ # Handle inheritance ($extends)
273
+ base_interface = None
274
+ base_properties = {}
275
+ base_required = []
276
+ if '$extends' in structure_schema:
277
+ base_ref = structure_schema['$extends']
278
+ base_schema = self.resolve_ref(base_ref, self.schema_doc)
279
+ if base_schema:
280
+ ref_path = base_ref.split('/')
281
+ base_name = ref_path[-1]
282
+ ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else parent_namespace
283
+ base_interface = self.generate_class(base_schema, ref_namespace, write_file=True, explicit_name=base_name)
284
+ # Collect base properties to include in the concrete type
285
+ base_properties = base_schema.get('properties', {})
286
+ base_required = base_schema.get('required', [])
287
+
288
+ # Generate properties - merge base properties with current properties
289
+ properties = {**base_properties, **structure_schema.get('properties', {})}
290
+ required_props = base_required + structure_schema.get('required', [])
291
+
292
+ fields = []
293
+ for prop_name, prop_schema in properties.items():
294
+ is_required = prop_name in required_props if not isinstance(required_props, list) or \
295
+ len(required_props) == 0 or not isinstance(required_props[0], list) else \
296
+ any(prop_name in req_set for req_set in required_props)
297
+
298
+ field_type = self.convert_structure_type_to_go(
299
+ class_name, prop_name, prop_schema, schema_namespace, nullable=not is_required)
300
+
301
+ # Add nullable marker if not required and not already nullable
302
+ if not is_required and not field_type.startswith('*') and not field_type.startswith('[') and not field_type.startswith('map[') and field_type != 'interface{}':
303
+ field_type = f'*{field_type}'
304
+
305
+ fields.append({
306
+ 'name': pascal(prop_name),
307
+ 'type': field_type,
308
+ 'original_name': prop_name
309
+ })
310
+
311
+ # Get imports needed
312
+ imports = self.get_imports_for_fields([f['type'] for f in fields])
313
+
314
+ # Generate Avro schema if avro_annotation is enabled
315
+ avro_schema_str = None
316
+ if self.avro_annotation:
317
+ try:
318
+ from avrotize.jstructtoavro import JsonStructureToAvro
319
+ converter = JsonStructureToAvro()
320
+ avro_schema = converter.convert(structure_schema)
321
+ avro_schema_str = json.dumps(avro_schema)
322
+ except Exception as e:
323
+ # If conversion fails, log but continue without Avro schema
324
+ print(f"Warning: Failed to generate Avro schema for {go_struct_name}: {e}")
325
+
326
+ context = {
327
+ 'doc': structure_schema.get('description', structure_schema.get('doc', class_name)),
328
+ 'struct_name': go_struct_name,
329
+ 'fields': fields,
330
+ 'imports': imports,
331
+ 'json_annotation': self.json_annotation,
332
+ 'avro_annotation': self.avro_annotation,
333
+ 'avro_schema': avro_schema_str,
334
+ 'base_package': self.base_package,
335
+ 'base_interface': base_interface,
336
+ 'referenced_packages': set(),
337
+ }
338
+
339
+ pkg_dir = os.path.join(self.output_dir, 'pkg', self.base_package)
340
+ if not os.path.exists(pkg_dir):
341
+ os.makedirs(pkg_dir, exist_ok=True)
342
+ file_name = os.path.join(pkg_dir, f"{go_struct_name}.go")
343
+ render_template('structuretogo/go_struct.jinja', file_name, **context)
344
+
345
+ self.structs.append({
346
+ 'name': go_struct_name,
347
+ 'fields': fields,
348
+ })
349
+
350
+ self.generate_unit_test('struct', go_struct_name, fields)
351
+
352
+ return go_struct_name
353
+
354
+ def generate_enum(self, structure_schema: Dict, field_name: str, parent_namespace: str,
355
+ write_file: bool) -> str:
356
+ """ Generates a Go enum from JSON Structure enum """
357
+ enum_name = pascal(structure_schema.get('name', field_name + 'Enum'))
358
+ schema_namespace = structure_schema.get('namespace', parent_namespace)
359
+ go_enum_name = self.go_type_name(enum_name, schema_namespace)
360
+
361
+ if go_enum_name in self.generated_types:
362
+ return go_enum_name
363
+
364
+ self.generated_types[go_enum_name] = "enum"
365
+ self.generated_structure_types[go_enum_name] = structure_schema
366
+
367
+ symbols = structure_schema.get('enum', [])
368
+
369
+ # Determine base type
370
+ base_type = structure_schema.get('type', 'string')
371
+ go_base_type = self.map_primitive_to_go(base_type, False)
372
+
373
+ context = {
374
+ 'doc': structure_schema.get('description', structure_schema.get('doc', enum_name)),
375
+ 'enum_name': go_enum_name,
376
+ 'symbols': symbols,
377
+ 'base_type': go_base_type,
378
+ 'base_package': self.base_package,
379
+ }
380
+
381
+ pkg_dir = os.path.join(self.output_dir, 'pkg', self.base_package)
382
+ if not os.path.exists(pkg_dir):
383
+ os.makedirs(pkg_dir, exist_ok=True)
384
+ file_name = os.path.join(pkg_dir, f"{go_enum_name}.go")
385
+ render_template('structuretogo/go_enum.jinja', file_name, **context)
386
+
387
+ self.enums.append({
388
+ 'name': go_enum_name,
389
+ 'symbols': symbols,
390
+ })
391
+
392
+ self.generate_unit_test('enum', go_enum_name, symbols)
393
+
394
+ return go_enum_name
395
+
396
+ def generate_interface(self, structure_schema: Dict, parent_namespace: str,
397
+ write_file: bool, explicit_name: str = '') -> str:
398
+ """ Generates a Go interface from JSON Structure abstract type """
399
+ interface_name = pascal(explicit_name if explicit_name else structure_schema.get('name', 'UnnamedInterface'))
400
+ schema_namespace = structure_schema.get('namespace', parent_namespace)
401
+ go_interface_name = self.go_type_name(interface_name, schema_namespace)
402
+
403
+ if go_interface_name in self.generated_types:
404
+ return go_interface_name
405
+
406
+ self.generated_types[go_interface_name] = "interface"
407
+ self.generated_structure_types[go_interface_name] = structure_schema
408
+
409
+ # Get properties to define getter methods
410
+ properties = structure_schema.get('properties', {})
411
+ required_props = structure_schema.get('required', [])
412
+
413
+ methods = []
414
+ for prop_name, prop_schema in properties.items():
415
+ go_prop_name = pascal(prop_name)
416
+ is_required = prop_name in required_props if not isinstance(required_props, list) or \
417
+ len(required_props) == 0 or not isinstance(required_props[0], list) else \
418
+ any(prop_name in req_set for req_set in required_props)
419
+
420
+ field_type = self.convert_structure_type_to_go(
421
+ interface_name, prop_name, prop_schema, schema_namespace, nullable=not is_required)
422
+
423
+ # Add nullable marker if not required and not already nullable
424
+ if not is_required and not field_type.startswith('*') and not field_type.startswith('[') and not field_type.startswith('map[') and field_type != 'interface{}':
425
+ field_type = f'*{field_type}'
426
+
427
+ # Generate getter method
428
+ methods.append({
429
+ 'name': f'Get{go_prop_name}',
430
+ 'return_type': field_type,
431
+ 'doc': f'Get{go_prop_name} returns the {prop_name} field'
432
+ })
433
+
434
+ # Generate setter method
435
+ methods.append({
436
+ 'name': f'Set{go_prop_name}',
437
+ 'return_type': '',
438
+ 'param_type': field_type,
439
+ 'param_name': prop_name,
440
+ 'doc': f'Set{go_prop_name} sets the {prop_name} field'
441
+ })
442
+
443
+ context = {
444
+ 'doc': structure_schema.get('description', structure_schema.get('doc', interface_name)),
445
+ 'interface_name': go_interface_name,
446
+ 'methods': methods,
447
+ 'base_package': self.base_package,
448
+ }
449
+
450
+ pkg_dir = os.path.join(self.output_dir, 'pkg', self.base_package)
451
+ if not os.path.exists(pkg_dir):
452
+ os.makedirs(pkg_dir, exist_ok=True)
453
+ file_name = os.path.join(pkg_dir, f"{go_interface_name}.go")
454
+ render_template('structuretogo/go_interface.jinja', file_name, **context)
455
+
456
+ return go_interface_name
457
+
458
+ def generate_choice(self, structure_schema: Dict, parent_namespace: str,
459
+ write_file: bool, explicit_name: str = '') -> str:
460
+ """ Generates a choice/union type """
461
+ # For simplicity, generate as interface{} for now
462
+ # A complete implementation would generate proper union types
463
+ return 'interface{}'
464
+
465
+ def generate_tuple(self, structure_schema: Dict, parent_namespace: str,
466
+ write_file: bool, explicit_name: str = '') -> str:
467
+ """ Generates a tuple type """
468
+ # For simplicity, generate as interface{} for now
469
+ return 'interface{}'
470
+
471
+ def generate_union_class(self, field_name: str, structure_types: List, parent_namespace: str) -> str:
472
+ """ Generates a union type """
473
+ # For simplicity, generate as interface{} for now
474
+ return 'interface{}'
475
+
476
+ def get_imports_for_fields(self, types: List[str]) -> Set[str]:
477
+ """Collects necessary imports based on the Go types"""
478
+ imports = set()
479
+ for field_type in types:
480
+ if "time.Time" in field_type or "time.Duration" in field_type:
481
+ imports.add("time")
482
+ if "big.Int" in field_type:
483
+ imports.add("math/big")
484
+ return imports
485
+
486
+ def random_value(self, go_type: str) -> str:
487
+ """Generates a random value for a given Go type"""
488
+ import random
489
+ import string
490
+
491
+ is_optional = False
492
+ if go_type.startswith('*'):
493
+ is_optional = True
494
+ go_type = go_type[1:]
495
+
496
+ if go_type == 'string':
497
+ v = '"' + ''.join(random.choices(string.ascii_letters + string.digits, k=10)) + '"'
498
+ elif go_type == 'bool':
499
+ v = 'true' if random.choice([True, False]) else 'false'
500
+ elif go_type == 'int':
501
+ v = str(random.randint(-100, 100))
502
+ elif go_type == 'int8':
503
+ v = f'int8({random.randint(-100, 100)})'
504
+ elif go_type == 'int16':
505
+ v = f'int16({random.randint(-100, 100)})'
506
+ elif go_type == 'int32':
507
+ v = f'int32({random.randint(-100, 100)})'
508
+ elif go_type == 'int64':
509
+ v = f'int64({random.randint(-100, 100)})'
510
+ elif go_type == 'uint':
511
+ v = f'uint({random.randint(0, 200)})'
512
+ elif go_type == 'uint8':
513
+ v = f'uint8({random.randint(0, 200)})'
514
+ elif go_type == 'uint16':
515
+ v = f'uint16({random.randint(0, 200)})'
516
+ elif go_type == 'uint32':
517
+ v = f'uint32({random.randint(0, 200)})'
518
+ elif go_type == 'uint64':
519
+ v = f'uint64({random.randint(0, 200)})'
520
+ elif go_type == 'float32':
521
+ v = f'float32({random.uniform(-100, 100)})'
522
+ elif go_type == 'float64':
523
+ v = f'float64({random.uniform(-100, 100)})'
524
+ elif go_type == '[]byte':
525
+ v = '[]byte("' + ''.join(random.choices(string.ascii_letters + string.digits, k=10)) + '")'
526
+ elif go_type == 'time.Time':
527
+ v = 'time.Now()'
528
+ elif go_type == 'time.Duration':
529
+ v = 'time.Hour'
530
+ elif go_type.startswith('[]'):
531
+ inner_type = go_type[2:]
532
+ v = f'{go_type}{{{self.random_value(inner_type)}}}'
533
+ elif go_type.startswith('map['):
534
+ # Extract value type from map[KeyType]ValueType
535
+ value_type = go_type.split(']', 1)[1]
536
+ v = f'{go_type}{{"key": {self.random_value(value_type)}}}'
537
+ elif go_type in self.generated_types:
538
+ v = f'CreateInstance{go_type}()'
539
+ elif go_type == 'interface{}':
540
+ v = 'nil'
541
+ else:
542
+ v = 'nil'
543
+
544
+ if is_optional and v != 'nil':
545
+ # Create a helper function to get pointer with proper type
546
+ return f'func() *{go_type} {{ v := {v}; return &v }}()'
547
+ return v
548
+
549
+ def generate_helpers(self) -> None:
550
+ """Generates helper functions for initializing structs with random values"""
551
+ context = {
552
+ 'structs': self.structs,
553
+ 'enums': self.enums,
554
+ 'base_package': self.base_package,
555
+ }
556
+ needs_time_import = False
557
+ for struct in context['structs']:
558
+ for field in struct['fields']:
559
+ if 'value' not in field:
560
+ field['value'] = self.random_value(field['type'])
561
+ # Check if time package is needed
562
+ if 'time.Time' in field['type'] or 'time.Duration' in field['type']:
563
+ needs_time_import = True
564
+ context['needs_time_import'] = needs_time_import
565
+ helpers_file_name = os.path.join(self.output_dir, 'pkg', self.base_package, f"{self.base_package}_helpers.go")
566
+ render_template('structuretogo/go_helpers.jinja', helpers_file_name, **context)
567
+
568
+ def generate_unit_test(self, kind: str, name: str, fields: Any):
569
+ """Generates unit tests for Go struct or enum"""
570
+ context = {
571
+ 'struct_name': name,
572
+ 'fields': fields,
573
+ 'kind': kind,
574
+ 'base_package': self.base_package,
575
+ 'package_site': self.package_site,
576
+ 'package_username': self.package_username,
577
+ 'json_annotation': self.json_annotation,
578
+ 'avro_annotation': self.avro_annotation
579
+ }
580
+
581
+ pkg_dir = os.path.join(self.output_dir, 'pkg', self.base_package)
582
+ if not os.path.exists(pkg_dir):
583
+ os.makedirs(pkg_dir, exist_ok=True)
584
+ test_file_name = os.path.join(pkg_dir, f"{name}_test.go")
585
+ render_template('structuretogo/go_test.jinja', test_file_name, **context)
586
+
587
+ def write_go_mod_file(self):
588
+ """Writes the go.mod file for the Go project"""
589
+ go_mod_content = ""
590
+ go_mod_content += "module " + self.package_site + "/" + self.package_username + "/" + self.base_package + "\n\n"
591
+ go_mod_content += "go 1.21\n\n"
592
+ if self.avro_annotation:
593
+ go_mod_content += "require (\n"
594
+ go_mod_content += " github.com/hamba/avro/v2 v2.27.0\n"
595
+ go_mod_content += ")\n"
596
+
597
+ go_mod_path = os.path.join(self.output_dir, "go.mod")
598
+ with open(go_mod_path, 'w', encoding='utf-8') as file:
599
+ file.write(go_mod_content)
600
+
601
+ def write_modname_go_file(self):
602
+ """Writes the modname.go file for the Go project"""
603
+ modname_go_content = ""
604
+ modname_go_content += "package " + self.base_package + "\n\n"
605
+ modname_go_content += "const ModName = \"" + self.base_package + "\"\n"
606
+
607
+ modname_go_path = os.path.join(self.output_dir, 'pkg', self.base_package, "module.go")
608
+ with open(modname_go_path, 'w', encoding='utf-8') as file:
609
+ file.write(modname_go_content)
610
+
611
+ def process_definitions(self, definitions: Dict, namespace_path: str) -> None:
612
+ """ Processes the definitions section recursively """
613
+ for name, definition in definitions.items():
614
+ if isinstance(definition, dict):
615
+ if 'type' in definition or 'enum' in definition:
616
+ # This is a type definition
617
+ current_namespace = namespace_path
618
+ # Check if this type was already generated
619
+ check_name = self.go_type_name(name, current_namespace)
620
+ if check_name not in self.generated_types:
621
+ if 'enum' in definition:
622
+ self.generate_enum(definition, name, current_namespace, write_file=True)
623
+ else:
624
+ self.generate_class_or_choice(definition, current_namespace, write_file=True, explicit_name=name)
625
+ else:
626
+ # This is a nested namespace
627
+ new_namespace = f"{namespace_path}.{name}" if namespace_path else name
628
+ self.process_definitions(definition, new_namespace)
629
+
630
+ def convert_schema(self, schema: JsonNode, output_dir: str):
631
+ """Converts JSON Structure schema to Go"""
632
+ if not isinstance(schema, list):
633
+ schema = [schema]
634
+ if not os.path.exists(output_dir):
635
+ os.makedirs(output_dir, exist_ok=True)
636
+ self.output_dir = output_dir
637
+
638
+ self.structs = []
639
+ self.enums = []
640
+
641
+ # Register all schemas with $id keywords for cross-references
642
+ for structure_schema in (s for s in schema if isinstance(s, dict)):
643
+ self.register_schema_ids(structure_schema)
644
+
645
+ for structure_schema in (s for s in schema if isinstance(s, dict)):
646
+ self.schema_doc = structure_schema
647
+
648
+ # Store definitions for later use
649
+ if 'definitions' in structure_schema:
650
+ self.definitions = structure_schema['definitions']
651
+
652
+ # Process root type
653
+ if 'enum' in structure_schema:
654
+ self.generate_enum(structure_schema, structure_schema.get('name', 'Enum'),
655
+ structure_schema.get('namespace', ''), write_file=True)
656
+ elif 'type' in structure_schema:
657
+ self.generate_class_or_choice(structure_schema, '', write_file=True)
658
+ elif '$root' in structure_schema:
659
+ root_ref = structure_schema['$root']
660
+ root_schema = self.resolve_ref(root_ref, structure_schema)
661
+ if root_schema:
662
+ ref_path = root_ref.split('/')
663
+ type_name = ref_path[-1]
664
+ ref_namespace = '.'.join(ref_path[2:-1]) if len(ref_path) > 3 else ''
665
+ self.generate_class_or_choice(root_schema, ref_namespace, write_file=True, explicit_name=type_name)
666
+
667
+ # Process remaining definitions
668
+ if 'definitions' in structure_schema:
669
+ self.process_definitions(self.definitions, '')
670
+
671
+ self.write_go_mod_file()
672
+ self.write_modname_go_file()
673
+ self.generate_helpers()
674
+
675
+ def convert(self, structure_schema_path: str, output_dir: str):
676
+ """Converts JSON Structure schema to Go"""
677
+ if not self.base_package:
678
+ self.base_package = os.path.splitext(os.path.basename(structure_schema_path))[0].replace('-', '_').lower()
679
+
680
+ with open(structure_schema_path, 'r', encoding='utf-8') as file:
681
+ schema = json.load(file)
682
+ self.convert_schema(schema, output_dir)
683
+
684
+
685
+ def convert_structure_to_go(structure_schema_path: str, go_file_path: str, package_name: str = '',
686
+ json_annotation: bool = False, avro_annotation: bool = False,
687
+ package_site: str = 'github.com', package_username: str = 'username'):
688
+ """Converts JSON Structure schema to Go structs
689
+
690
+ Args:
691
+ structure_schema_path (str): JSON Structure input schema path
692
+ go_file_path (str): Output Go directory path
693
+ package_name (str): Base package name
694
+ json_annotation (bool): Include JSON annotations
695
+ avro_annotation (bool): Include Avro annotations
696
+ package_site (str): Package site for Go module
697
+ package_username (str): Package username for Go module
698
+ """
699
+ if not package_name:
700
+ package_name = os.path.splitext(os.path.basename(structure_schema_path))[0].replace('-', '_').lower()
701
+
702
+ structuretogo = StructureToGo(package_name)
703
+ structuretogo.json_annotation = json_annotation
704
+ structuretogo.avro_annotation = avro_annotation
705
+ structuretogo.package_site = package_site
706
+ structuretogo.package_username = package_username
707
+ structuretogo.convert(structure_schema_path, go_file_path)
708
+
709
+
710
+ def convert_structure_schema_to_go(structure_schema: JsonNode, output_dir: str, package_name: str = '',
711
+ json_annotation: bool = False, avro_annotation: bool = False,
712
+ package_site: str = 'github.com', package_username: str = 'username'):
713
+ """Converts JSON Structure schema to Go structs
714
+
715
+ Args:
716
+ structure_schema (JsonNode): JSON Structure schema as a dictionary or list of dictionaries
717
+ output_dir (str): Output directory path
718
+ package_name (str): Base package name
719
+ json_annotation (bool): Include JSON annotations
720
+ avro_annotation (bool): Include Avro annotations
721
+ package_site (str): Package site for Go module
722
+ package_username (str): Package username for Go module
723
+ """
724
+ structuretogo = StructureToGo(package_name)
725
+ structuretogo.json_annotation = json_annotation
726
+ structuretogo.avro_annotation = avro_annotation
727
+ structuretogo.package_site = package_site
728
+ structuretogo.package_username = package_username
729
+ structuretogo.convert_schema(structure_schema, output_dir)