polyxml 0.14.3__tar.gz → 0.15.0__tar.gz
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.
- {polyxml-0.14.3 → polyxml-0.15.0}/Cargo.lock +5 -5
- {polyxml-0.14.3 → polyxml-0.15.0}/Cargo.toml +1 -1
- {polyxml-0.14.3 → polyxml-0.15.0}/PKG-INFO +1 -1
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/java/mod.rs +164 -12
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/mod.rs +1 -1
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_java_codegen.rs +280 -1
- {polyxml-0.14.3 → polyxml-0.15.0}/pyproject.toml +1 -1
- {polyxml-0.14.3 → polyxml-0.15.0}/README.md +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/Cargo.toml +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/README.md +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/converters.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/error.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/json.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/lib.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/parser.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/schema.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/serializer.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/transcoder.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/value.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_core.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_json.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_security.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/Cargo.toml +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/README.md +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/src/lib.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/tests/test_json.py +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/python/polyxml/AGENT_GUIDE.md +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/python/polyxml/__init__.py +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/python/polyxml/compat/__init__.py +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/python/polyxml/compat/xsdata.py +0 -0
- {polyxml-0.14.3 → polyxml-0.15.0}/python/polyxml/py.typed +0 -0
|
@@ -549,7 +549,7 @@ dependencies = [
|
|
|
549
549
|
|
|
550
550
|
[[package]]
|
|
551
551
|
name = "polyxml"
|
|
552
|
-
version = "0.
|
|
552
|
+
version = "0.15.0"
|
|
553
553
|
dependencies = [
|
|
554
554
|
"criterion",
|
|
555
555
|
"heck",
|
|
@@ -567,14 +567,14 @@ dependencies = [
|
|
|
567
567
|
|
|
568
568
|
[[package]]
|
|
569
569
|
name = "polyxml-c"
|
|
570
|
-
version = "0.
|
|
570
|
+
version = "0.15.0"
|
|
571
571
|
dependencies = [
|
|
572
572
|
"polyxml",
|
|
573
573
|
]
|
|
574
574
|
|
|
575
575
|
[[package]]
|
|
576
576
|
name = "polyxml-cli"
|
|
577
|
-
version = "0.
|
|
577
|
+
version = "0.15.0"
|
|
578
578
|
dependencies = [
|
|
579
579
|
"clap",
|
|
580
580
|
"glob",
|
|
@@ -588,7 +588,7 @@ dependencies = [
|
|
|
588
588
|
|
|
589
589
|
[[package]]
|
|
590
590
|
name = "polyxml-js"
|
|
591
|
-
version = "0.
|
|
591
|
+
version = "0.15.0"
|
|
592
592
|
dependencies = [
|
|
593
593
|
"napi",
|
|
594
594
|
"napi-build",
|
|
@@ -598,7 +598,7 @@ dependencies = [
|
|
|
598
598
|
|
|
599
599
|
[[package]]
|
|
600
600
|
name = "polyxml-python"
|
|
601
|
-
version = "0.
|
|
601
|
+
version = "0.15.0"
|
|
602
602
|
dependencies = [
|
|
603
603
|
"lexical-core",
|
|
604
604
|
"polyxml",
|
|
@@ -10,11 +10,37 @@ use crate::ir::{
|
|
|
10
10
|
TypeRef, UnionDef,
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
/// Target backend for Java code emission.
|
|
14
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
|
15
|
+
pub enum JavaBackend {
|
|
16
|
+
/// Zero-dependency Java 21+ records (default).
|
|
17
|
+
#[default]
|
|
18
|
+
Standard,
|
|
19
|
+
/// Jackson-annotated records for Spring Boot / enterprise JSON+XML binding.
|
|
20
|
+
Jackson,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl JavaBackend {
|
|
24
|
+
pub fn from_str_loose(s: &str) -> Option<Self> {
|
|
25
|
+
match s.to_lowercase().trim() {
|
|
26
|
+
"standard" | "std" | "default" => Some(Self::Standard),
|
|
27
|
+
"jackson" | "spring" | "spring-boot" | "enterprise" => Some(Self::Jackson),
|
|
28
|
+
_ => None,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub fn is_jackson(self) -> bool {
|
|
33
|
+
matches!(self, Self::Jackson)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
13
37
|
/// Options configuring Java 21+ code generation.
|
|
14
38
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
15
39
|
pub struct JavaOptions {
|
|
16
40
|
/// Java package declaration for generated types (default: "generated.models")
|
|
17
41
|
pub package_name: String,
|
|
42
|
+
/// Target serialization backend (default: Standard / zero-dependency).
|
|
43
|
+
pub backend: JavaBackend,
|
|
18
44
|
/// Use Java 21 `record` instead of traditional classes (default: true)
|
|
19
45
|
pub use_records: bool,
|
|
20
46
|
/// Generate facet validation in compact constructors (default: true)
|
|
@@ -27,6 +53,7 @@ impl Default for JavaOptions {
|
|
|
27
53
|
fn default() -> Self {
|
|
28
54
|
Self {
|
|
29
55
|
package_name: "generated.models".to_string(),
|
|
56
|
+
backend: JavaBackend::Standard,
|
|
30
57
|
use_records: true,
|
|
31
58
|
validate_facets: true,
|
|
32
59
|
emit_root_aliases: true,
|
|
@@ -244,7 +271,11 @@ impl JavaCodegen {
|
|
|
244
271
|
|
|
245
272
|
fn emit_file_header(&self, out: &mut String) {
|
|
246
273
|
out.push_str("// Generated by PolyXML Compiler (https://github.com/nth-bailey/PolyXML)\n");
|
|
247
|
-
|
|
274
|
+
if self.options.backend.is_jackson() {
|
|
275
|
+
out.push_str("// Target: Java 21+ (Records, Jackson XML/JSON Annotations)\n\n");
|
|
276
|
+
} else {
|
|
277
|
+
out.push_str("// Target: Java 21+ (Records, Sealed Interfaces, Pattern Matching)\n\n");
|
|
278
|
+
}
|
|
248
279
|
|
|
249
280
|
if !self.options.package_name.is_empty() {
|
|
250
281
|
let _ = writeln!(out, "package {};\n", self.options.package_name);
|
|
@@ -253,7 +284,15 @@ impl JavaCodegen {
|
|
|
253
284
|
out.push_str("import java.util.*;\n");
|
|
254
285
|
out.push_str("import java.time.*;\n");
|
|
255
286
|
out.push_str("import java.math.*;\n");
|
|
256
|
-
out.push_str("import java.util.regex.Pattern;\n
|
|
287
|
+
out.push_str("import java.util.regex.Pattern;\n");
|
|
288
|
+
|
|
289
|
+
if self.options.backend.is_jackson() {
|
|
290
|
+
out.push('\n');
|
|
291
|
+
out.push_str("import com.fasterxml.jackson.annotation.*;\n");
|
|
292
|
+
out.push_str("import com.fasterxml.jackson.dataformat.xml.annotation.*;\n");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
out.push('\n');
|
|
257
296
|
}
|
|
258
297
|
|
|
259
298
|
fn generate_single_type(&self, type_def: &TypeDef, is_nested: bool) -> (String, String) {
|
|
@@ -300,6 +339,25 @@ impl JavaCodegen {
|
|
|
300
339
|
self.emit_docstring(out, doc, indent);
|
|
301
340
|
}
|
|
302
341
|
|
|
342
|
+
let jackson = self.options.backend.is_jackson();
|
|
343
|
+
|
|
344
|
+
// Jackson class-level annotations
|
|
345
|
+
if jackson {
|
|
346
|
+
let _ = writeln!(out, "{}@JsonIgnoreProperties(ignoreUnknown = true)", indent);
|
|
347
|
+
let _ = writeln!(out, "{}@JsonInclude(JsonInclude.Include.NON_EMPTY)", indent);
|
|
348
|
+
let ns_attr = s
|
|
349
|
+
.qname
|
|
350
|
+
.namespace
|
|
351
|
+
.as_deref()
|
|
352
|
+
.map(|ns| format!(", namespace = {:?}", ns))
|
|
353
|
+
.unwrap_or_default();
|
|
354
|
+
let _ = writeln!(
|
|
355
|
+
out,
|
|
356
|
+
"{}@JacksonXmlRootElement(localName = {:?}{})",
|
|
357
|
+
indent, s.qname.local, ns_attr
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
303
361
|
let mut seen_fields = HashSet::new();
|
|
304
362
|
let mut components = Vec::new();
|
|
305
363
|
let mut validation_checks = Vec::new();
|
|
@@ -323,7 +381,38 @@ impl JavaCodegen {
|
|
|
323
381
|
self.context.map_type_ref(&field.type_ref)
|
|
324
382
|
};
|
|
325
383
|
|
|
326
|
-
|
|
384
|
+
if jackson {
|
|
385
|
+
let mut annotations = Vec::new();
|
|
386
|
+
// @JsonProperty
|
|
387
|
+
annotations.push(format!("@JsonProperty({:?})", field.xml_name));
|
|
388
|
+
|
|
389
|
+
// @JacksonXmlProperty
|
|
390
|
+
let is_attr = field.kind == crate::ir::FieldKind::Attribute;
|
|
391
|
+
let ns_part = field
|
|
392
|
+
.namespace
|
|
393
|
+
.as_deref()
|
|
394
|
+
.map(|ns| format!(", namespace = {:?}", ns))
|
|
395
|
+
.unwrap_or_default();
|
|
396
|
+
annotations.push(format!(
|
|
397
|
+
"@JacksonXmlProperty(localName = {:?}, isAttribute = {}{})",
|
|
398
|
+
field.xml_name, is_attr, ns_part
|
|
399
|
+
));
|
|
400
|
+
|
|
401
|
+
// List fields: @JacksonXmlElementWrapper(useWrapping = false)
|
|
402
|
+
if is_list {
|
|
403
|
+
annotations.push("@JacksonXmlElementWrapper(useWrapping = false)".to_string());
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Optional/list fields: @JsonInclude(NON_EMPTY)
|
|
407
|
+
if is_optional || is_list {
|
|
408
|
+
annotations.push("@JsonInclude(JsonInclude.Include.NON_EMPTY)".to_string());
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
let annotation_str = annotations.join(" ");
|
|
412
|
+
components.push(format!("{} {} {}", annotation_str, field_type, field_id));
|
|
413
|
+
} else {
|
|
414
|
+
components.push(format!("{} {}", field_type, field_id));
|
|
415
|
+
}
|
|
327
416
|
|
|
328
417
|
if self.options.validate_facets {
|
|
329
418
|
if let Some(ref facets) = field.facets {
|
|
@@ -373,6 +462,8 @@ impl JavaCodegen {
|
|
|
373
462
|
self.emit_docstring(out, doc, indent);
|
|
374
463
|
}
|
|
375
464
|
|
|
465
|
+
let jackson = self.options.backend.is_jackson();
|
|
466
|
+
|
|
376
467
|
let modifier = if indent.is_empty() {
|
|
377
468
|
"public "
|
|
378
469
|
} else {
|
|
@@ -404,10 +495,18 @@ impl JavaCodegen {
|
|
|
404
495
|
let _ = writeln!(out, "{} this.value = value;", indent);
|
|
405
496
|
let _ = writeln!(out, "{} }}\n", indent);
|
|
406
497
|
|
|
498
|
+
// @JsonValue on getValue()
|
|
499
|
+
if jackson {
|
|
500
|
+
let _ = writeln!(out, "{} @JsonValue", indent);
|
|
501
|
+
}
|
|
407
502
|
let _ = writeln!(out, "{} public String getValue() {{", indent);
|
|
408
503
|
let _ = writeln!(out, "{} return this.value;", indent);
|
|
409
504
|
let _ = writeln!(out, "{} }}\n", indent);
|
|
410
505
|
|
|
506
|
+
// @JsonCreator on fromValue()
|
|
507
|
+
if jackson {
|
|
508
|
+
let _ = writeln!(out, "{} @JsonCreator", indent);
|
|
509
|
+
}
|
|
411
510
|
let _ = writeln!(
|
|
412
511
|
out,
|
|
413
512
|
"{} public static {} fromValue(String value) {{",
|
|
@@ -433,6 +532,8 @@ impl JavaCodegen {
|
|
|
433
532
|
self.emit_docstring(out, doc, indent);
|
|
434
533
|
}
|
|
435
534
|
|
|
535
|
+
let jackson = self.options.backend.is_jackson();
|
|
536
|
+
|
|
436
537
|
let modifier = if indent.is_empty() {
|
|
437
538
|
"public "
|
|
438
539
|
} else {
|
|
@@ -451,6 +552,29 @@ impl JavaCodegen {
|
|
|
451
552
|
.collect::<Vec<_>>()
|
|
452
553
|
.join(", ");
|
|
453
554
|
|
|
555
|
+
// Jackson polymorphic type annotations
|
|
556
|
+
if jackson {
|
|
557
|
+
let _ = writeln!(
|
|
558
|
+
out,
|
|
559
|
+
"{}@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = \"@type\")",
|
|
560
|
+
indent
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
let subtypes: Vec<String> = u
|
|
564
|
+
.branches
|
|
565
|
+
.iter()
|
|
566
|
+
.map(|b| {
|
|
567
|
+
let vn = to_java_type_name(&b.variant_name);
|
|
568
|
+
format!(
|
|
569
|
+
"@JsonSubTypes.Type(value = {}.{}.class, name = {:?})",
|
|
570
|
+
java_name, vn, b.xml_name
|
|
571
|
+
)
|
|
572
|
+
})
|
|
573
|
+
.collect();
|
|
574
|
+
|
|
575
|
+
let _ = writeln!(out, "{}@JsonSubTypes({{{}}})", indent, subtypes.join(", "));
|
|
576
|
+
}
|
|
577
|
+
|
|
454
578
|
let _ = writeln!(
|
|
455
579
|
out,
|
|
456
580
|
"{}{}sealed interface {} permits {} {{",
|
|
@@ -465,6 +589,10 @@ impl JavaCodegen {
|
|
|
465
589
|
self.emit_docstring(out, doc, &format!("{} ", indent));
|
|
466
590
|
}
|
|
467
591
|
|
|
592
|
+
if jackson {
|
|
593
|
+
let _ = writeln!(out, "{} @JsonTypeName({:?})", indent, branch.xml_name);
|
|
594
|
+
}
|
|
595
|
+
|
|
468
596
|
let _ = writeln!(
|
|
469
597
|
out,
|
|
470
598
|
"{} record {}({} value) implements {} {{}}",
|
|
@@ -480,6 +608,7 @@ impl JavaCodegen {
|
|
|
480
608
|
self.emit_docstring(out, doc, indent);
|
|
481
609
|
}
|
|
482
610
|
|
|
611
|
+
let jackson = self.options.backend.is_jackson();
|
|
483
612
|
let base_type = self.context.map_type_ref(&s.base_type);
|
|
484
613
|
let modifier = if indent.is_empty() {
|
|
485
614
|
"public "
|
|
@@ -493,25 +622,48 @@ impl JavaCodegen {
|
|
|
493
622
|
Vec::new()
|
|
494
623
|
};
|
|
495
624
|
|
|
496
|
-
|
|
625
|
+
// Build the value component with optional Jackson annotations
|
|
626
|
+
let value_component = if jackson {
|
|
627
|
+
format!("@JsonValue @JacksonXmlText {} value", base_type)
|
|
628
|
+
} else {
|
|
629
|
+
format!("{} value", base_type)
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
if checks.is_empty() && !jackson {
|
|
497
633
|
let _ = writeln!(
|
|
498
634
|
out,
|
|
499
|
-
"{}{}record {}({}
|
|
500
|
-
indent, modifier, java_name,
|
|
635
|
+
"{}{}record {}({}) {{}}",
|
|
636
|
+
indent, modifier, java_name, value_component
|
|
501
637
|
);
|
|
502
638
|
return;
|
|
503
639
|
}
|
|
504
640
|
|
|
505
641
|
let _ = writeln!(
|
|
506
642
|
out,
|
|
507
|
-
"{}{}record {}({}
|
|
508
|
-
indent, modifier, java_name,
|
|
643
|
+
"{}{}record {}({}) {{",
|
|
644
|
+
indent, modifier, java_name, value_component
|
|
509
645
|
);
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
646
|
+
|
|
647
|
+
// Jackson @JsonCreator factory method
|
|
648
|
+
if jackson {
|
|
649
|
+
let _ = writeln!(out, "{} @JsonCreator", indent);
|
|
650
|
+
let _ = writeln!(
|
|
651
|
+
out,
|
|
652
|
+
"{} public static {} of({} value) {{",
|
|
653
|
+
indent, java_name, base_type
|
|
654
|
+
);
|
|
655
|
+
let _ = writeln!(out, "{} return new {}(value);", indent, java_name);
|
|
656
|
+
let _ = writeln!(out, "{} }}", indent);
|
|
513
657
|
}
|
|
514
|
-
|
|
658
|
+
|
|
659
|
+
if !checks.is_empty() {
|
|
660
|
+
let _ = writeln!(out, "{} public {} {{", indent, java_name);
|
|
661
|
+
for check in checks {
|
|
662
|
+
let _ = writeln!(out, "{} {}", indent, check);
|
|
663
|
+
}
|
|
664
|
+
let _ = writeln!(out, "{} }}", indent);
|
|
665
|
+
}
|
|
666
|
+
|
|
515
667
|
let _ = writeln!(out, "{}}}", indent);
|
|
516
668
|
}
|
|
517
669
|
|
|
@@ -16,7 +16,7 @@ pub mod typescript;
|
|
|
16
16
|
pub use cpp::{CppCodegen, CppMode, CppOptions};
|
|
17
17
|
pub use csharp::{CSharpCodegen, CSharpOptions, CSharpRecordKind};
|
|
18
18
|
pub use go::{GoCodegen, GoOptions};
|
|
19
|
-
pub use java::{JavaCodegen, JavaOptions};
|
|
19
|
+
pub use java::{JavaBackend, JavaCodegen, JavaOptions};
|
|
20
20
|
pub use python::{PythonBackend, PythonCodegen, PythonOptions};
|
|
21
21
|
pub use rust::{RustCodegen, RustOptions};
|
|
22
22
|
pub use typescript::{TypeScriptCodegen, TypeScriptOptions};
|
|
@@ -3,7 +3,8 @@ use std::process::Command;
|
|
|
3
3
|
use tempfile::tempdir;
|
|
4
4
|
|
|
5
5
|
use polyxml::codegen::java::{
|
|
6
|
-
to_java_enum_constant, to_java_field_identifier, to_java_type_name,
|
|
6
|
+
to_java_enum_constant, to_java_field_identifier, to_java_type_name, JavaBackend, JavaCodegen,
|
|
7
|
+
JavaOptions,
|
|
7
8
|
};
|
|
8
9
|
use polyxml::ir::{
|
|
9
10
|
Cardinality, EnumDef, EnumValue, FieldDef, FieldKind, PrimitiveType, QName, RestrictionFacets,
|
|
@@ -153,6 +154,7 @@ fn test_java_records_and_enums_generation() {
|
|
|
153
154
|
|
|
154
155
|
let options = JavaOptions {
|
|
155
156
|
package_name: "com.example.shop".to_string(),
|
|
157
|
+
backend: JavaBackend::Standard,
|
|
156
158
|
use_records: true,
|
|
157
159
|
validate_facets: true,
|
|
158
160
|
emit_root_aliases: true,
|
|
@@ -239,6 +241,7 @@ fn test_java_sealed_interface_choice() {
|
|
|
239
241
|
|
|
240
242
|
let options = JavaOptions {
|
|
241
243
|
package_name: "com.example.payment".to_string(),
|
|
244
|
+
backend: JavaBackend::Standard,
|
|
242
245
|
use_records: true,
|
|
243
246
|
validate_facets: true,
|
|
244
247
|
emit_root_aliases: true,
|
|
@@ -291,6 +294,7 @@ fn test_java_module_container_class() {
|
|
|
291
294
|
|
|
292
295
|
let options = JavaOptions {
|
|
293
296
|
package_name: "com.example.banking".to_string(),
|
|
297
|
+
backend: JavaBackend::Standard,
|
|
294
298
|
use_records: true,
|
|
295
299
|
validate_facets: true,
|
|
296
300
|
emit_root_aliases: true,
|
|
@@ -327,3 +331,278 @@ fn test_java_module_container_class() {
|
|
|
327
331
|
);
|
|
328
332
|
}
|
|
329
333
|
}
|
|
334
|
+
|
|
335
|
+
#[test]
|
|
336
|
+
fn test_java_jackson_backend_struct_annotations() {
|
|
337
|
+
let mut ir = SchemaIR::new().with_target_namespace("https://example.com/crm");
|
|
338
|
+
|
|
339
|
+
ir.add_type(TypeDef::Struct(StructDef {
|
|
340
|
+
qname: QName::new(Some("https://example.com/crm"), "Contact"),
|
|
341
|
+
base_type: None,
|
|
342
|
+
is_abstract: false,
|
|
343
|
+
fields: vec![
|
|
344
|
+
FieldDef {
|
|
345
|
+
name: "id".into(),
|
|
346
|
+
xml_name: "id".into(),
|
|
347
|
+
namespace: None,
|
|
348
|
+
kind: FieldKind::Attribute,
|
|
349
|
+
type_ref: TypeRef::Primitive(PrimitiveType::Int),
|
|
350
|
+
cardinality: Cardinality::required_one(),
|
|
351
|
+
nillable: false,
|
|
352
|
+
default_value: None,
|
|
353
|
+
fixed_value: None,
|
|
354
|
+
documentation: None,
|
|
355
|
+
facets: None,
|
|
356
|
+
is_cycle_cut: false,
|
|
357
|
+
},
|
|
358
|
+
FieldDef {
|
|
359
|
+
name: "email".into(),
|
|
360
|
+
xml_name: "email".into(),
|
|
361
|
+
namespace: None,
|
|
362
|
+
kind: FieldKind::Element,
|
|
363
|
+
type_ref: TypeRef::Primitive(PrimitiveType::String),
|
|
364
|
+
cardinality: Cardinality::required_one(),
|
|
365
|
+
nillable: false,
|
|
366
|
+
default_value: None,
|
|
367
|
+
fixed_value: None,
|
|
368
|
+
documentation: None,
|
|
369
|
+
facets: None,
|
|
370
|
+
is_cycle_cut: false,
|
|
371
|
+
},
|
|
372
|
+
FieldDef {
|
|
373
|
+
name: "phone".into(),
|
|
374
|
+
xml_name: "phone".into(),
|
|
375
|
+
namespace: None,
|
|
376
|
+
kind: FieldKind::Element,
|
|
377
|
+
type_ref: TypeRef::Primitive(PrimitiveType::String),
|
|
378
|
+
cardinality: Cardinality::optional_one(),
|
|
379
|
+
nillable: true,
|
|
380
|
+
default_value: None,
|
|
381
|
+
fixed_value: None,
|
|
382
|
+
documentation: None,
|
|
383
|
+
facets: None,
|
|
384
|
+
is_cycle_cut: false,
|
|
385
|
+
},
|
|
386
|
+
FieldDef {
|
|
387
|
+
name: "tags".into(),
|
|
388
|
+
xml_name: "tag".into(),
|
|
389
|
+
namespace: None,
|
|
390
|
+
kind: FieldKind::Element,
|
|
391
|
+
type_ref: TypeRef::Primitive(PrimitiveType::String),
|
|
392
|
+
cardinality: Cardinality::unbounded(0),
|
|
393
|
+
nillable: false,
|
|
394
|
+
default_value: None,
|
|
395
|
+
fixed_value: None,
|
|
396
|
+
documentation: None,
|
|
397
|
+
facets: None,
|
|
398
|
+
is_cycle_cut: false,
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
documentation: Some("CRM contact record".into()),
|
|
402
|
+
}));
|
|
403
|
+
|
|
404
|
+
let options = JavaOptions {
|
|
405
|
+
package_name: "com.example.crm".to_string(),
|
|
406
|
+
backend: JavaBackend::Jackson,
|
|
407
|
+
use_records: true,
|
|
408
|
+
validate_facets: true,
|
|
409
|
+
emit_root_aliases: true,
|
|
410
|
+
};
|
|
411
|
+
let codegen = JavaCodegen::new(options);
|
|
412
|
+
let files = codegen.generate_files(&ir);
|
|
413
|
+
|
|
414
|
+
assert_eq!(files.len(), 1);
|
|
415
|
+
let (filename, code) = &files[0];
|
|
416
|
+
assert_eq!(filename, "Contact.java");
|
|
417
|
+
|
|
418
|
+
// Jackson imports
|
|
419
|
+
assert!(code.contains("import com.fasterxml.jackson.annotation.*;"));
|
|
420
|
+
assert!(code.contains("import com.fasterxml.jackson.dataformat.xml.annotation.*;"));
|
|
421
|
+
|
|
422
|
+
// Class-level annotations
|
|
423
|
+
assert!(code.contains("@JsonIgnoreProperties(ignoreUnknown = true)"));
|
|
424
|
+
assert!(code.contains("@JsonInclude(JsonInclude.Include.NON_EMPTY)"));
|
|
425
|
+
assert!(code.contains("@JacksonXmlRootElement(localName = \"Contact\""));
|
|
426
|
+
|
|
427
|
+
// Field annotations — attribute
|
|
428
|
+
assert!(code.contains("@JsonProperty(\"id\")"));
|
|
429
|
+
assert!(code.contains("@JacksonXmlProperty(localName = \"id\", isAttribute = true)"));
|
|
430
|
+
|
|
431
|
+
// Field annotations — element
|
|
432
|
+
assert!(code.contains("@JsonProperty(\"email\")"));
|
|
433
|
+
assert!(code.contains("@JacksonXmlProperty(localName = \"email\", isAttribute = false)"));
|
|
434
|
+
|
|
435
|
+
// Optional field gets NON_EMPTY
|
|
436
|
+
assert!(code.contains("@JsonProperty(\"phone\")"));
|
|
437
|
+
|
|
438
|
+
// List field gets wrapper suppression
|
|
439
|
+
assert!(code.contains("@JacksonXmlElementWrapper(useWrapping = false)"));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
#[test]
|
|
443
|
+
fn test_java_jackson_backend_enum_annotations() {
|
|
444
|
+
let mut ir = SchemaIR::new().with_target_namespace("https://example.com/orders");
|
|
445
|
+
|
|
446
|
+
ir.add_type(TypeDef::Enum(EnumDef {
|
|
447
|
+
qname: QName::new(Some("https://example.com/orders"), "Priority"),
|
|
448
|
+
base_type: TypeRef::Primitive(PrimitiveType::String),
|
|
449
|
+
variants: vec![
|
|
450
|
+
EnumValue {
|
|
451
|
+
name: "low".into(),
|
|
452
|
+
value: "low".into(),
|
|
453
|
+
documentation: None,
|
|
454
|
+
},
|
|
455
|
+
EnumValue {
|
|
456
|
+
name: "high".into(),
|
|
457
|
+
value: "high".into(),
|
|
458
|
+
documentation: None,
|
|
459
|
+
},
|
|
460
|
+
],
|
|
461
|
+
documentation: None,
|
|
462
|
+
}));
|
|
463
|
+
|
|
464
|
+
let options = JavaOptions {
|
|
465
|
+
package_name: "com.example.orders".to_string(),
|
|
466
|
+
backend: JavaBackend::Jackson,
|
|
467
|
+
use_records: true,
|
|
468
|
+
validate_facets: true,
|
|
469
|
+
emit_root_aliases: true,
|
|
470
|
+
};
|
|
471
|
+
let codegen = JavaCodegen::new(options);
|
|
472
|
+
let files = codegen.generate_files(&ir);
|
|
473
|
+
|
|
474
|
+
assert_eq!(files.len(), 1);
|
|
475
|
+
let (_, code) = &files[0];
|
|
476
|
+
|
|
477
|
+
// @JsonValue on getValue()
|
|
478
|
+
assert!(code.contains("@JsonValue"));
|
|
479
|
+
assert!(code.contains("public String getValue()"));
|
|
480
|
+
|
|
481
|
+
// @JsonCreator on fromValue()
|
|
482
|
+
assert!(code.contains("@JsonCreator"));
|
|
483
|
+
assert!(code.contains("public static Priority fromValue(String value)"));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
#[test]
|
|
487
|
+
fn test_java_jackson_backend_union_annotations() {
|
|
488
|
+
let mut ir = SchemaIR::new().with_target_namespace("https://example.com/messaging");
|
|
489
|
+
|
|
490
|
+
ir.add_type(TypeDef::Union(UnionDef {
|
|
491
|
+
qname: QName::new(Some("https://example.com/messaging"), "MessageChannel"),
|
|
492
|
+
branches: vec![
|
|
493
|
+
UnionBranch {
|
|
494
|
+
variant_name: "sms".into(),
|
|
495
|
+
xml_name: "sms".into(),
|
|
496
|
+
namespace: None,
|
|
497
|
+
type_ref: TypeRef::Primitive(PrimitiveType::String),
|
|
498
|
+
documentation: None,
|
|
499
|
+
},
|
|
500
|
+
UnionBranch {
|
|
501
|
+
variant_name: "email".into(),
|
|
502
|
+
xml_name: "email".into(),
|
|
503
|
+
namespace: None,
|
|
504
|
+
type_ref: TypeRef::Primitive(PrimitiveType::String),
|
|
505
|
+
documentation: None,
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
documentation: None,
|
|
509
|
+
}));
|
|
510
|
+
|
|
511
|
+
let options = JavaOptions {
|
|
512
|
+
package_name: "com.example.messaging".to_string(),
|
|
513
|
+
backend: JavaBackend::Jackson,
|
|
514
|
+
use_records: true,
|
|
515
|
+
validate_facets: true,
|
|
516
|
+
emit_root_aliases: true,
|
|
517
|
+
};
|
|
518
|
+
let codegen = JavaCodegen::new(options);
|
|
519
|
+
let files = codegen.generate_files(&ir);
|
|
520
|
+
|
|
521
|
+
assert_eq!(files.len(), 1);
|
|
522
|
+
let (_, code) = &files[0];
|
|
523
|
+
|
|
524
|
+
// Polymorphic type info
|
|
525
|
+
assert!(code.contains("@JsonTypeInfo(use = JsonTypeInfo.Id.NAME"));
|
|
526
|
+
assert!(code.contains("@JsonSubTypes({"));
|
|
527
|
+
assert!(code.contains("@JsonSubTypes.Type(value = MessageChannel.Sms.class, name = \"sms\")"));
|
|
528
|
+
assert!(
|
|
529
|
+
code.contains("@JsonSubTypes.Type(value = MessageChannel.Email.class, name = \"email\")")
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
// Variant type names
|
|
533
|
+
assert!(code.contains("@JsonTypeName(\"sms\")"));
|
|
534
|
+
assert!(code.contains("@JsonTypeName(\"email\")"));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
#[test]
|
|
538
|
+
fn test_java_jackson_backend_simple_type_annotations() {
|
|
539
|
+
let mut ir = SchemaIR::new().with_target_namespace("https://example.com/types");
|
|
540
|
+
|
|
541
|
+
ir.add_type(TypeDef::Simple(Box::new(SimpleTypeDef {
|
|
542
|
+
qname: QName::new(Some("https://example.com/types"), "CurrencyCode"),
|
|
543
|
+
base_type: TypeRef::Primitive(PrimitiveType::String),
|
|
544
|
+
facets: RestrictionFacets {
|
|
545
|
+
length: Some(3),
|
|
546
|
+
patterns: vec!["^[A-Z]{3}$".into()],
|
|
547
|
+
..Default::default()
|
|
548
|
+
},
|
|
549
|
+
documentation: Some("ISO 4217 currency code".into()),
|
|
550
|
+
})));
|
|
551
|
+
|
|
552
|
+
let options = JavaOptions {
|
|
553
|
+
package_name: "com.example.types".to_string(),
|
|
554
|
+
backend: JavaBackend::Jackson,
|
|
555
|
+
use_records: true,
|
|
556
|
+
validate_facets: true,
|
|
557
|
+
emit_root_aliases: true,
|
|
558
|
+
};
|
|
559
|
+
let codegen = JavaCodegen::new(options);
|
|
560
|
+
let files = codegen.generate_files(&ir);
|
|
561
|
+
|
|
562
|
+
assert_eq!(files.len(), 1);
|
|
563
|
+
let (_, code) = &files[0];
|
|
564
|
+
|
|
565
|
+
// @JsonValue @JacksonXmlText on value component
|
|
566
|
+
assert!(code.contains("@JsonValue @JacksonXmlText String value"));
|
|
567
|
+
|
|
568
|
+
// @JsonCreator factory method
|
|
569
|
+
assert!(code.contains("@JsonCreator"));
|
|
570
|
+
assert!(code.contains("public static CurrencyCode of(String value)"));
|
|
571
|
+
assert!(code.contains("return new CurrencyCode(value);"));
|
|
572
|
+
|
|
573
|
+
// Validation still present
|
|
574
|
+
assert!(code.contains("if (value.length() != 3)"));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
#[test]
|
|
578
|
+
fn test_java_jackson_backend_from_str_loose() {
|
|
579
|
+
assert_eq!(
|
|
580
|
+
JavaBackend::from_str_loose("jackson"),
|
|
581
|
+
Some(JavaBackend::Jackson)
|
|
582
|
+
);
|
|
583
|
+
assert_eq!(
|
|
584
|
+
JavaBackend::from_str_loose("Jackson"),
|
|
585
|
+
Some(JavaBackend::Jackson)
|
|
586
|
+
);
|
|
587
|
+
assert_eq!(
|
|
588
|
+
JavaBackend::from_str_loose("spring"),
|
|
589
|
+
Some(JavaBackend::Jackson)
|
|
590
|
+
);
|
|
591
|
+
assert_eq!(
|
|
592
|
+
JavaBackend::from_str_loose("spring-boot"),
|
|
593
|
+
Some(JavaBackend::Jackson)
|
|
594
|
+
);
|
|
595
|
+
assert_eq!(
|
|
596
|
+
JavaBackend::from_str_loose("enterprise"),
|
|
597
|
+
Some(JavaBackend::Jackson)
|
|
598
|
+
);
|
|
599
|
+
assert_eq!(
|
|
600
|
+
JavaBackend::from_str_loose("standard"),
|
|
601
|
+
Some(JavaBackend::Standard)
|
|
602
|
+
);
|
|
603
|
+
assert_eq!(
|
|
604
|
+
JavaBackend::from_str_loose("std"),
|
|
605
|
+
Some(JavaBackend::Standard)
|
|
606
|
+
);
|
|
607
|
+
assert_eq!(JavaBackend::from_str_loose("unknown"), None);
|
|
608
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|