avrotize 2.21.0__py3-none-any.whl → 2.22.0__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/_version.py +2 -2
- avrotize/avrotogo.py +21 -8
- avrotize/avrotojava/class_test.java.jinja +2 -4
- avrotize/avrotojava.py +67 -5
- avrotize/avrotopython/dataclass_core.jinja +12 -2
- avrotize/avrotopython/test_class.jinja +2 -1
- avrotize/avrotopython.py +48 -2
- avrotize/avrotorust/dataclass_enum.rs.jinja +9 -7
- avrotize/avrotorust/dataclass_struct.rs.jinja +19 -11
- avrotize/avrotorust/dataclass_union.rs.jinja +25 -7
- avrotize/avrotorust.py +64 -26
- avrotize/structuretocsharp.py +42 -11
- avrotize/structuretodb.py +21 -0
- avrotize/structuretogo.py +38 -10
- avrotize/structuretojava/pom.xml.jinja +5 -0
- avrotize/structuretojava.py +114 -8
- avrotize/structuretopython/dataclass_core.jinja +2 -1
- avrotize/structuretopython.py +100 -19
- avrotize/structuretots.py +8 -5
- {avrotize-2.21.0.dist-info → avrotize-2.22.0.dist-info}/METADATA +1 -1
- {avrotize-2.21.0.dist-info → avrotize-2.22.0.dist-info}/RECORD +24 -24
- {avrotize-2.21.0.dist-info → avrotize-2.22.0.dist-info}/WHEEL +0 -0
- {avrotize-2.21.0.dist-info → avrotize-2.22.0.dist-info}/entry_points.txt +0 -0
- {avrotize-2.21.0.dist-info → avrotize-2.22.0.dist-info}/licenses/LICENSE +0 -0
avrotize/avrotorust.py
CHANGED
|
@@ -144,12 +144,15 @@ class AvroToRust:
|
|
|
144
144
|
field_name = self.safe_identifier(snake(original_field_name))
|
|
145
145
|
field_type = self.convert_avro_type_to_rust(field_name, field['type'], parent_namespace)
|
|
146
146
|
serde_rename = field_name != original_field_name
|
|
147
|
+
# Check if this is a generated type (enum, union, or record) where random values may match default
|
|
148
|
+
is_generated_type = field_type in self.generated_types_rust_package or '::' in field_type
|
|
147
149
|
fields.append({
|
|
148
150
|
'original_name': original_field_name,
|
|
149
151
|
'name': field_name,
|
|
150
152
|
'type': field_type,
|
|
151
153
|
'serde_rename': serde_rename,
|
|
152
|
-
'random_value': self.generate_random_value(field_type)
|
|
154
|
+
'random_value': self.generate_random_value(field_type),
|
|
155
|
+
'is_generated_type': is_generated_type
|
|
153
156
|
})
|
|
154
157
|
|
|
155
158
|
struct_name = self.safe_identifier(pascal(avro_schema['name']))
|
|
@@ -187,28 +190,51 @@ class AvroToRust:
|
|
|
187
190
|
def get_is_json_match_clause(self, field_name: str, field_type: str, for_union=False) -> str:
|
|
188
191
|
"""Generates the is_json_match clause for a field"""
|
|
189
192
|
ref = f'node[\"{field_name}\"]' if not for_union else 'node'
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return f"{ref}.
|
|
202
|
-
elif
|
|
203
|
-
return f"{ref}.
|
|
204
|
-
elif
|
|
205
|
-
return f"{ref}.
|
|
206
|
-
elif
|
|
207
|
-
return f"{ref}.
|
|
208
|
-
elif
|
|
209
|
-
return f"{ref}.
|
|
193
|
+
|
|
194
|
+
# Check if type is optional - if so, we need to allow null values
|
|
195
|
+
is_optional = field_type.startswith('Option<')
|
|
196
|
+
base_type = field_type[7:-1] if is_optional else field_type
|
|
197
|
+
null_check = f" || {ref}.is_null()" if is_optional else ""
|
|
198
|
+
|
|
199
|
+
# serde_json::Value can be any JSON type, so always return true
|
|
200
|
+
if base_type == 'serde_json::Value':
|
|
201
|
+
return "true"
|
|
202
|
+
|
|
203
|
+
if base_type == 'String':
|
|
204
|
+
return f"({ref}.is_string(){null_check})"
|
|
205
|
+
elif base_type == 'bool':
|
|
206
|
+
return f"({ref}.is_boolean(){null_check})"
|
|
207
|
+
elif base_type == 'i32':
|
|
208
|
+
return f"({ref}.is_i64(){null_check})"
|
|
209
|
+
elif base_type == 'i64':
|
|
210
|
+
return f"({ref}.is_i64(){null_check})"
|
|
211
|
+
elif base_type == 'f32':
|
|
212
|
+
return f"({ref}.is_f64(){null_check})"
|
|
213
|
+
elif base_type == 'f64':
|
|
214
|
+
return f"({ref}.is_f64(){null_check})"
|
|
215
|
+
elif base_type == 'Vec<u8>':
|
|
216
|
+
return f"({ref}.is_array(){null_check})"
|
|
217
|
+
elif base_type == 'std::collections::HashMap<String, String>':
|
|
218
|
+
return f"({ref}.is_object(){null_check})"
|
|
219
|
+
elif base_type.startswith('std::collections::HashMap<String, '):
|
|
220
|
+
return f"({ref}.is_object(){null_check})"
|
|
221
|
+
elif base_type.startswith('Vec<'):
|
|
222
|
+
return f"({ref}.is_array(){null_check})"
|
|
223
|
+
# chrono types - check for string (ISO 8601 format) or number (timestamp)
|
|
224
|
+
elif 'chrono::NaiveDateTime' in base_type or 'NaiveDateTime' in base_type:
|
|
225
|
+
return f"({ref}.is_string() || {ref}.is_i64(){null_check})"
|
|
226
|
+
elif 'chrono::NaiveDate' in base_type or 'NaiveDate' in base_type:
|
|
227
|
+
return f"({ref}.is_string() || {ref}.is_i64(){null_check})"
|
|
228
|
+
elif 'chrono::NaiveTime' in base_type or 'NaiveTime' in base_type:
|
|
229
|
+
return f"({ref}.is_string() || {ref}.is_i64(){null_check})"
|
|
230
|
+
# uuid type - check for string
|
|
231
|
+
elif 'uuid::Uuid' in base_type or 'Uuid' in base_type:
|
|
232
|
+
return f"({ref}.is_string(){null_check})"
|
|
210
233
|
else:
|
|
211
|
-
|
|
234
|
+
# Custom types - call their is_json_match method
|
|
235
|
+
if is_optional:
|
|
236
|
+
return f"({base_type}::is_json_match(&{ref}) || {ref}.is_null())"
|
|
237
|
+
return f"{base_type}::is_json_match(&{ref})"
|
|
212
238
|
|
|
213
239
|
|
|
214
240
|
def generate_enum(self, avro_schema: Dict, parent_namespace: str) -> str:
|
|
@@ -250,17 +276,29 @@ class AvroToRust:
|
|
|
250
276
|
ns = namespace.replace('.', '::').lower()
|
|
251
277
|
union_enum_name = pascal(field_name) + 'Union'
|
|
252
278
|
union_types = [self.convert_avro_type_to_rust(field_name + "Option" + str(i), t, namespace) for i, t in enumerate(avro_type) if t != 'null']
|
|
253
|
-
|
|
254
|
-
|
|
279
|
+
|
|
280
|
+
# Track seen predicates to identify structurally identical variants
|
|
281
|
+
seen_predicates: set = set()
|
|
282
|
+
union_fields = []
|
|
283
|
+
for i, t in enumerate(union_types):
|
|
284
|
+
predicate = self.get_is_json_match_clause(field_name, t, for_union=True)
|
|
285
|
+
# Mark if this is the first variant with this predicate structure
|
|
286
|
+
# Subsequent variants with same predicate can't be distinguished during JSON deserialization
|
|
287
|
+
is_first_with_predicate = predicate not in seen_predicates
|
|
288
|
+
seen_predicates.add(predicate)
|
|
289
|
+
union_fields.append({
|
|
255
290
|
'name': pascal(t.rsplit('::',1)[-1]),
|
|
256
291
|
'type': t,
|
|
257
292
|
'random_value': self.generate_random_value(t),
|
|
258
293
|
'default_value': 'Default::default()',
|
|
259
|
-
'json_match_predicate':
|
|
260
|
-
|
|
294
|
+
'json_match_predicate': predicate,
|
|
295
|
+
'is_first_with_predicate': is_first_with_predicate,
|
|
296
|
+
})
|
|
297
|
+
|
|
261
298
|
qualified_union_enum_name = self.safe_package(self.concat_package(ns, union_enum_name))
|
|
262
299
|
context = {
|
|
263
300
|
'serde_annotation': self.serde_annotation,
|
|
301
|
+
'avro_annotation': self.avro_annotation,
|
|
264
302
|
'union_enum_name': union_enum_name,
|
|
265
303
|
'union_fields': union_fields,
|
|
266
304
|
'json_match_predicates': [self.get_is_json_match_clause(f['name'], f['type'], for_union=True) for f in union_fields]
|
avrotize/structuretocsharp.py
CHANGED
|
@@ -143,6 +143,35 @@ class StructureToCSharp:
|
|
|
143
143
|
]
|
|
144
144
|
return word in reserved_words
|
|
145
145
|
|
|
146
|
+
def safe_identifier(self, name: str, class_name: str = '', fallback_prefix: str = 'field') -> str:
|
|
147
|
+
"""Converts a name to a safe C# identifier.
|
|
148
|
+
|
|
149
|
+
Handles:
|
|
150
|
+
- Reserved words (prepend @)
|
|
151
|
+
- Numeric prefixes (prepend _)
|
|
152
|
+
- Special characters (replace with _)
|
|
153
|
+
- All-special-char names (use fallback_prefix)
|
|
154
|
+
- Class name collision (append _)
|
|
155
|
+
"""
|
|
156
|
+
import re
|
|
157
|
+
# Replace invalid characters with underscores
|
|
158
|
+
safe = re.sub(r'[^a-zA-Z0-9_]', '_', str(name))
|
|
159
|
+
# Remove leading/trailing underscores from sanitization
|
|
160
|
+
safe = safe.strip('_') if safe != name else safe
|
|
161
|
+
# If nothing left after removing special chars, use fallback
|
|
162
|
+
if not safe or not re.match(r'^[a-zA-Z_@]', safe):
|
|
163
|
+
if safe and re.match(r'^[0-9]', safe):
|
|
164
|
+
safe = '_' + safe # Numeric prefix
|
|
165
|
+
else:
|
|
166
|
+
safe = fallback_prefix + '_' + (safe if safe else 'unnamed')
|
|
167
|
+
# Handle reserved words with @ prefix
|
|
168
|
+
if self.is_csharp_reserved_word(safe):
|
|
169
|
+
safe = '@' + safe
|
|
170
|
+
# Handle class name collision
|
|
171
|
+
if class_name and safe == class_name:
|
|
172
|
+
safe = safe + '_'
|
|
173
|
+
return safe
|
|
174
|
+
|
|
146
175
|
def is_csharp_primitive_type(self, csharp_type: str) -> bool:
|
|
147
176
|
""" Checks if a type is a C# primitive type """
|
|
148
177
|
if csharp_type.endswith('?'):
|
|
@@ -416,16 +445,18 @@ class StructureToCSharp:
|
|
|
416
445
|
""" Generates a property for a class """
|
|
417
446
|
property_definition = ''
|
|
418
447
|
|
|
419
|
-
# Resolve property name
|
|
420
|
-
field_name = prop_name
|
|
421
|
-
if self.is_csharp_reserved_word(field_name):
|
|
422
|
-
field_name = f"@{field_name}"
|
|
448
|
+
# Resolve property name using safe_identifier for special chars, numeric prefixes, etc.
|
|
449
|
+
field_name = self.safe_identifier(prop_name, class_name)
|
|
423
450
|
if self.pascal_properties:
|
|
424
|
-
field_name_cs = pascal(field_name)
|
|
451
|
+
field_name_cs = pascal(field_name.lstrip('@'))
|
|
452
|
+
# Re-check for class name collision after pascal casing
|
|
453
|
+
if field_name_cs == class_name:
|
|
454
|
+
field_name_cs += "_"
|
|
425
455
|
else:
|
|
426
456
|
field_name_cs = field_name
|
|
427
|
-
|
|
428
|
-
|
|
457
|
+
|
|
458
|
+
# Track if field name differs from original for JSON annotation
|
|
459
|
+
needs_json_annotation = field_name_cs != prop_name
|
|
429
460
|
|
|
430
461
|
# Check if this is a const field
|
|
431
462
|
if 'const' in prop_schema:
|
|
@@ -442,9 +473,9 @@ class StructureToCSharp:
|
|
|
442
473
|
|
|
443
474
|
# Add JSON property name annotation when property name differs from schema name
|
|
444
475
|
# This is needed for proper JSON serialization/deserialization, especially with pascal_properties
|
|
445
|
-
if
|
|
476
|
+
if needs_json_annotation:
|
|
446
477
|
property_definition += f'{INDENT}[System.Text.Json.Serialization.JsonPropertyName("{prop_name}")]\n'
|
|
447
|
-
if self.newtonsoft_json_annotation and
|
|
478
|
+
if self.newtonsoft_json_annotation and needs_json_annotation:
|
|
448
479
|
property_definition += f'{INDENT}[Newtonsoft.Json.JsonProperty("{prop_name}")]\n'
|
|
449
480
|
|
|
450
481
|
# Add XML element annotation if enabled
|
|
@@ -473,9 +504,9 @@ class StructureToCSharp:
|
|
|
473
504
|
|
|
474
505
|
# Add JSON property name annotation when property name differs from schema name
|
|
475
506
|
# This is needed for proper JSON serialization/deserialization, especially with pascal_properties
|
|
476
|
-
if
|
|
507
|
+
if needs_json_annotation:
|
|
477
508
|
property_definition += f'{INDENT}[System.Text.Json.Serialization.JsonPropertyName("{prop_name}")]\n'
|
|
478
|
-
if self.newtonsoft_json_annotation and
|
|
509
|
+
if self.newtonsoft_json_annotation and needs_json_annotation:
|
|
479
510
|
property_definition += f'{INDENT}[Newtonsoft.Json.JsonProperty("{prop_name}")]\n'
|
|
480
511
|
|
|
481
512
|
# Add XML element annotation if enabled
|
avrotize/structuretodb.py
CHANGED
|
@@ -443,6 +443,27 @@ def structure_type_to_sql_type(structure_type: Any, dialect: str) -> str:
|
|
|
443
443
|
struct_type = structure_type.get("type", "string")
|
|
444
444
|
if struct_type in ["array", "set", "map", "object", "choice", "tuple"]:
|
|
445
445
|
return type_map[dialect][struct_type]
|
|
446
|
+
|
|
447
|
+
# Handle string type with maxLength annotation
|
|
448
|
+
if struct_type == "string" and "maxLength" in structure_type:
|
|
449
|
+
max_length = structure_type["maxLength"]
|
|
450
|
+
if dialect == "sqlserver" or dialect == "sqlanywhere":
|
|
451
|
+
return f"NVARCHAR({max_length})"
|
|
452
|
+
elif dialect in ["postgres", "redshift", "db2"]:
|
|
453
|
+
return f"VARCHAR({max_length})"
|
|
454
|
+
elif dialect in ["mysql", "mariadb"]:
|
|
455
|
+
return f"VARCHAR({max_length})"
|
|
456
|
+
elif dialect == "sqlite":
|
|
457
|
+
return f"VARCHAR({max_length})"
|
|
458
|
+
elif dialect == "oracle":
|
|
459
|
+
return f"VARCHAR2({max_length})"
|
|
460
|
+
elif dialect == "bigquery":
|
|
461
|
+
return f"STRING({max_length})"
|
|
462
|
+
elif dialect == "snowflake":
|
|
463
|
+
return f"VARCHAR({max_length})"
|
|
464
|
+
else:
|
|
465
|
+
return f"VARCHAR({max_length})"
|
|
466
|
+
|
|
446
467
|
return structure_type_to_sql_type(struct_type, dialect)
|
|
447
468
|
|
|
448
469
|
return type_map.get(dialect, type_map["postgres"])["string"]
|
avrotize/structuretogo.py
CHANGED
|
@@ -16,8 +16,15 @@ INDENT = ' '
|
|
|
16
16
|
class StructureToGo:
|
|
17
17
|
""" Converts JSON Structure schema to Go structs """
|
|
18
18
|
|
|
19
|
+
# Go reserved keywords that cannot be used as package names
|
|
20
|
+
GO_RESERVED_WORDS = [
|
|
21
|
+
'break', 'default', 'func', 'interface', 'select', 'case', 'defer', 'go', 'map', 'struct', 'chan',
|
|
22
|
+
'else', 'goto', 'package', 'switch', 'const', 'fallthrough', 'if', 'range', 'type', 'continue', 'for',
|
|
23
|
+
'import', 'return', 'var',
|
|
24
|
+
]
|
|
25
|
+
|
|
19
26
|
def __init__(self, base_package: str = '') -> None:
|
|
20
|
-
self.base_package = base_package
|
|
27
|
+
self.base_package = self._safe_package_name(base_package) if base_package else base_package
|
|
21
28
|
self.output_dir = os.getcwd()
|
|
22
29
|
self.json_annotation = False
|
|
23
30
|
self.avro_annotation = False
|
|
@@ -31,17 +38,37 @@ class StructureToGo:
|
|
|
31
38
|
self.structs: List[Dict] = []
|
|
32
39
|
self.enums: List[Dict] = []
|
|
33
40
|
|
|
34
|
-
def
|
|
35
|
-
"""Converts a name to a safe Go
|
|
36
|
-
|
|
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:
|
|
41
|
+
def _safe_package_name(self, name: str) -> str:
|
|
42
|
+
"""Converts a name to a safe Go package name"""
|
|
43
|
+
if name in self.GO_RESERVED_WORDS:
|
|
42
44
|
return f"{name}_"
|
|
43
45
|
return name
|
|
44
46
|
|
|
47
|
+
def safe_identifier(self, name: str, fallback_prefix: str = 'field') -> str:
|
|
48
|
+
"""Converts a name to a safe Go identifier.
|
|
49
|
+
|
|
50
|
+
Handles:
|
|
51
|
+
- Reserved words (append _)
|
|
52
|
+
- Numeric prefixes (prepend _)
|
|
53
|
+
- Special characters (replace with _)
|
|
54
|
+
- All-special-char names (use fallback_prefix)
|
|
55
|
+
"""
|
|
56
|
+
import re
|
|
57
|
+
# Replace invalid characters with underscores
|
|
58
|
+
safe = re.sub(r'[^a-zA-Z0-9_]', '_', str(name))
|
|
59
|
+
# Remove leading/trailing underscores from sanitization
|
|
60
|
+
safe = safe.strip('_') if safe != name else safe
|
|
61
|
+
# If nothing left after removing special chars, use fallback
|
|
62
|
+
if not safe or not re.match(r'^[a-zA-Z_]', safe):
|
|
63
|
+
if safe and re.match(r'^[0-9]', safe):
|
|
64
|
+
safe = '_' + safe # Numeric prefix
|
|
65
|
+
else:
|
|
66
|
+
safe = fallback_prefix + '_' + (safe if safe else 'unnamed')
|
|
67
|
+
# Handle reserved words
|
|
68
|
+
if safe in self.GO_RESERVED_WORDS:
|
|
69
|
+
safe = safe + '_'
|
|
70
|
+
return safe
|
|
71
|
+
|
|
45
72
|
def go_type_name(self, name: str, namespace: str = '') -> str:
|
|
46
73
|
"""Returns a qualified name for a Go struct or enum"""
|
|
47
74
|
if namespace:
|
|
@@ -675,7 +702,8 @@ class StructureToGo:
|
|
|
675
702
|
def convert(self, structure_schema_path: str, output_dir: str):
|
|
676
703
|
"""Converts JSON Structure schema to Go"""
|
|
677
704
|
if not self.base_package:
|
|
678
|
-
|
|
705
|
+
pkg_name = os.path.splitext(os.path.basename(structure_schema_path))[0].replace('-', '_').lower()
|
|
706
|
+
self.base_package = self._safe_package_name(pkg_name)
|
|
679
707
|
|
|
680
708
|
with open(structure_schema_path, 'r', encoding='utf-8') as file:
|
|
681
709
|
schema = json.load(file)
|
|
@@ -22,5 +22,10 @@
|
|
|
22
22
|
<artifactId>jackson-annotations</artifactId>
|
|
23
23
|
<version>${jackson.version}</version>
|
|
24
24
|
</dependency>
|
|
25
|
+
<dependency>
|
|
26
|
+
<groupId>com.fasterxml.jackson.datatype</groupId>
|
|
27
|
+
<artifactId>jackson-datatype-jsr310</artifactId>
|
|
28
|
+
<version>${jackson.version}</version>
|
|
29
|
+
</dependency>
|
|
25
30
|
</dependencies>
|
|
26
31
|
</project>
|
avrotize/structuretojava.py
CHANGED
|
@@ -71,13 +71,34 @@ class StructureToJava:
|
|
|
71
71
|
self.is_class = is_class
|
|
72
72
|
self.is_enum = is_enum
|
|
73
73
|
|
|
74
|
-
def safe_identifier(self, name: str, class_name: str = '') -> str:
|
|
75
|
-
"""Converts a name to a safe Java identifier
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
def safe_identifier(self, name: str, class_name: str = '', fallback_prefix: str = 'field') -> str:
|
|
75
|
+
"""Converts a name to a safe Java identifier.
|
|
76
|
+
|
|
77
|
+
Handles:
|
|
78
|
+
- Reserved words (prepend _)
|
|
79
|
+
- Numeric prefixes (prepend _)
|
|
80
|
+
- Special characters (replace with _)
|
|
81
|
+
- All-special-char names (use fallback_prefix)
|
|
82
|
+
- Class name collision (append _)
|
|
83
|
+
"""
|
|
84
|
+
import re
|
|
85
|
+
# Replace invalid characters with underscores
|
|
86
|
+
safe = re.sub(r'[^a-zA-Z0-9_]', '_', str(name))
|
|
87
|
+
# Remove leading/trailing underscores from sanitization
|
|
88
|
+
safe = safe.strip('_') if safe != name else safe
|
|
89
|
+
# If nothing left after removing special chars, use fallback
|
|
90
|
+
if not safe or not re.match(r'^[a-zA-Z_]', safe):
|
|
91
|
+
if safe and re.match(r'^[0-9]', safe):
|
|
92
|
+
safe = '_' + safe # Numeric prefix
|
|
93
|
+
else:
|
|
94
|
+
safe = fallback_prefix + '_' + (safe if safe else 'unnamed')
|
|
95
|
+
# Handle reserved words
|
|
96
|
+
if is_java_reserved_word(safe):
|
|
97
|
+
safe = '_' + safe
|
|
98
|
+
# Handle class name collision
|
|
99
|
+
if class_name and safe == class_name:
|
|
100
|
+
safe = safe + '_'
|
|
101
|
+
return safe
|
|
81
102
|
|
|
82
103
|
def safe_package(self, packageName: str) -> str:
|
|
83
104
|
"""Converts a name to a safe Java identifier by checking each path segment"""
|
|
@@ -384,7 +405,12 @@ class StructureToJava:
|
|
|
384
405
|
field_count=len(field_names)
|
|
385
406
|
)
|
|
386
407
|
|
|
387
|
-
|
|
408
|
+
# Generate createTestInstance() method for testing (only for non-abstract classes)
|
|
409
|
+
create_test_instance = ''
|
|
410
|
+
if not is_abstract:
|
|
411
|
+
create_test_instance = self.generate_create_test_instance_method(class_name, fields, schema_namespace)
|
|
412
|
+
|
|
413
|
+
class_definition = class_definition.rstrip() + create_test_instance + equals_hashcode + "\n}\n"
|
|
388
414
|
|
|
389
415
|
if write_file:
|
|
390
416
|
self.write_to_file(package, class_name, class_definition)
|
|
@@ -455,6 +481,86 @@ class StructureToJava:
|
|
|
455
481
|
return str(value)
|
|
456
482
|
return f"/* unsupported const value */"
|
|
457
483
|
|
|
484
|
+
def get_test_value(self, java_type: str) -> str:
|
|
485
|
+
""" Get a test value for a Java type """
|
|
486
|
+
# Handle arrays/lists
|
|
487
|
+
if java_type.startswith("List<") or java_type.startswith("ArrayList<"):
|
|
488
|
+
return "new java.util.ArrayList<>()"
|
|
489
|
+
if java_type.startswith("Map<"):
|
|
490
|
+
return "new java.util.HashMap<>()"
|
|
491
|
+
if java_type.endswith("[]"):
|
|
492
|
+
return f"new {java_type[:-2]}[0]"
|
|
493
|
+
|
|
494
|
+
# Primitive test values
|
|
495
|
+
test_values = {
|
|
496
|
+
'String': '"test-string"',
|
|
497
|
+
'string': '"test-string"',
|
|
498
|
+
'int': '42',
|
|
499
|
+
'Integer': '42',
|
|
500
|
+
'long': '42L',
|
|
501
|
+
'Long': '42L',
|
|
502
|
+
'double': '3.14',
|
|
503
|
+
'Double': '3.14',
|
|
504
|
+
'float': '3.14f',
|
|
505
|
+
'Float': '3.14f',
|
|
506
|
+
'boolean': 'true',
|
|
507
|
+
'Boolean': 'true',
|
|
508
|
+
'byte': '(byte)0',
|
|
509
|
+
'Byte': '(byte)0',
|
|
510
|
+
'short': '(short)0',
|
|
511
|
+
'Short': '(short)0',
|
|
512
|
+
'BigInteger': 'java.math.BigInteger.ZERO',
|
|
513
|
+
'BigDecimal': 'java.math.BigDecimal.ZERO',
|
|
514
|
+
'byte[]': 'new byte[0]',
|
|
515
|
+
'LocalDate': 'java.time.LocalDate.now()',
|
|
516
|
+
'LocalTime': 'java.time.LocalTime.now()',
|
|
517
|
+
'Instant': 'java.time.Instant.now()',
|
|
518
|
+
'Duration': 'java.time.Duration.ZERO',
|
|
519
|
+
'UUID': 'java.util.UUID.randomUUID()',
|
|
520
|
+
'URI': 'java.net.URI.create("http://example.com")',
|
|
521
|
+
'Object': 'new Object()',
|
|
522
|
+
'void': 'null',
|
|
523
|
+
'Void': 'null',
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if java_type in test_values:
|
|
527
|
+
return test_values[java_type]
|
|
528
|
+
|
|
529
|
+
# Check if it's a generated type (enum or class)
|
|
530
|
+
if java_type in self.generated_types_java_package:
|
|
531
|
+
type_kind = self.generated_types_java_package[java_type]
|
|
532
|
+
if type_kind == "enum":
|
|
533
|
+
return f'{java_type}.values()[0]'
|
|
534
|
+
elif type_kind == "class":
|
|
535
|
+
return f'{java_type}.createTestInstance()'
|
|
536
|
+
|
|
537
|
+
# Default: try to instantiate
|
|
538
|
+
return f'new {java_type}()'
|
|
539
|
+
|
|
540
|
+
def generate_create_test_instance_method(self, class_name: str, fields: List[Dict], parent_package: str) -> str:
|
|
541
|
+
""" Generates a static createTestInstance method that creates a fully initialized instance """
|
|
542
|
+
method = f"\n{INDENT}/**\n{INDENT} * Creates a test instance with all required fields populated\n{INDENT} * @return a fully initialized test instance\n{INDENT} */\n"
|
|
543
|
+
method += f"{INDENT}public static {class_name} createTestInstance() {{\n"
|
|
544
|
+
method += f"{INDENT*2}{class_name} instance = new {class_name}();\n"
|
|
545
|
+
|
|
546
|
+
for field in fields:
|
|
547
|
+
# Skip const fields
|
|
548
|
+
if field.get('is_const', False):
|
|
549
|
+
continue
|
|
550
|
+
|
|
551
|
+
field_name = field['name']
|
|
552
|
+
field_type = field['type']
|
|
553
|
+
|
|
554
|
+
# Get a test value for this field
|
|
555
|
+
test_value = self.get_test_value(field_type)
|
|
556
|
+
|
|
557
|
+
# Setter name: set{Pascal(field_name)}
|
|
558
|
+
method += f"{INDENT*2}instance.set{pascal(field_name)}({test_value});\n"
|
|
559
|
+
|
|
560
|
+
method += f"{INDENT*2}return instance;\n"
|
|
561
|
+
method += f"{INDENT}}}\n"
|
|
562
|
+
return method
|
|
563
|
+
|
|
458
564
|
def generate_enum(self, structure_schema: Dict, field_name: str, parent_package: str, write_file: bool) -> JavaType:
|
|
459
565
|
""" Generates a Java enum from JSON Structure enum schema """
|
|
460
566
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
""" {{ class_name }} dataclass. """
|
|
2
2
|
|
|
3
3
|
# pylint: disable=too-many-lines, too-many-locals, too-many-branches, too-many-statements, too-many-arguments, line-too-long, wildcard-import
|
|
4
|
+
from __future__ import annotations
|
|
4
5
|
|
|
5
6
|
{%- if avro_annotation or dataclasses_json_annotation %}
|
|
6
7
|
import io
|
|
@@ -69,7 +70,7 @@ class {{ class_name }}{% if base_class or is_abstract %}({% if base_class %}{{ b
|
|
|
69
70
|
{% endif %}
|
|
70
71
|
{% for field in fields %}
|
|
71
72
|
{%- set isdate = field.type == "datetime.datetime" or field.type == "typing.Optional[datetime.datetime]" %}
|
|
72
|
-
{{ field.name }}: {{ field.type }}=dataclasses.field(kw_only=True{% if dataclasses_json_annotation %}, metadata=dataclasses_json.config(field_name="{{ field.original_name }}"{%- if isdate -%}, encoder=lambda d:
|
|
73
|
+
{{ field.name }}: {{ field.type }}=dataclasses.field(kw_only=True{% if dataclasses_json_annotation %}, metadata=dataclasses_json.config(field_name="{{ field.original_name }}"{%- if isdate -%}, encoder=lambda d: d.isoformat() if isinstance(d, datetime.datetime) else d if d else None, decoder=lambda d: datetime.datetime.fromisoformat(d) if isinstance(d, str) else d if d else None, mm_field=fields.DateTime(format='iso'){%- endif -%}){%- endif %})
|
|
73
74
|
{%- endfor %}
|
|
74
75
|
|
|
75
76
|
@classmethod
|