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.
Files changed (54) hide show
  1. {polyxml-0.14.3 → polyxml-0.15.0}/Cargo.lock +5 -5
  2. {polyxml-0.14.3 → polyxml-0.15.0}/Cargo.toml +1 -1
  3. {polyxml-0.14.3 → polyxml-0.15.0}/PKG-INFO +1 -1
  4. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/java/mod.rs +164 -12
  5. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/mod.rs +1 -1
  6. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_java_codegen.rs +280 -1
  7. {polyxml-0.14.3 → polyxml-0.15.0}/pyproject.toml +1 -1
  8. {polyxml-0.14.3 → polyxml-0.15.0}/README.md +0 -0
  9. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/Cargo.toml +0 -0
  10. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/README.md +0 -0
  11. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  12. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  13. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
  14. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
  15. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
  16. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
  17. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  18. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/converters.rs +0 -0
  19. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/error.rs +0 -0
  20. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
  21. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  22. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/json.rs +0 -0
  23. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/lib.rs +0 -0
  24. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/parser.rs +0 -0
  25. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/schema.rs +0 -0
  26. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
  27. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/serializer.rs +0 -0
  28. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/transcoder.rs +0 -0
  29. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/src/value.rs +0 -0
  30. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  31. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_core.rs +0 -0
  32. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  33. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
  34. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
  35. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_json.rs +0 -0
  36. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  37. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  38. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
  39. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  40. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_security.rs +0 -0
  41. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  42. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  43. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/Cargo.toml +0 -0
  44. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/README.md +0 -0
  45. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/src/lib.rs +0 -0
  46. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
  47. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  48. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/tests/test_json.py +0 -0
  49. {polyxml-0.14.3 → polyxml-0.15.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  50. {polyxml-0.14.3 → polyxml-0.15.0}/python/polyxml/AGENT_GUIDE.md +0 -0
  51. {polyxml-0.14.3 → polyxml-0.15.0}/python/polyxml/__init__.py +0 -0
  52. {polyxml-0.14.3 → polyxml-0.15.0}/python/polyxml/compat/__init__.py +0 -0
  53. {polyxml-0.14.3 → polyxml-0.15.0}/python/polyxml/compat/xsdata.py +0 -0
  54. {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.14.3"
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.14.3"
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.14.3"
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.14.3"
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.14.3"
601
+ version = "0.15.0"
602
602
  dependencies = [
603
603
  "lexical-core",
604
604
  "polyxml",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/polyxml-core", "crates/polyxml-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.14.3"
6
+ version = "0.15.0"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyxml
3
- Version: 0.14.3
3
+ Version: 0.15.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -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
- out.push_str("// Target: Java 21+ (Records, Sealed Interfaces, Pattern Matching)\n\n");
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\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
- components.push(format!("{} {}", field_type, field_id));
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
- if checks.is_empty() {
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 {}({} value) {{}}",
500
- indent, modifier, java_name, base_type
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 {}({} value) {{",
508
- indent, modifier, java_name, base_type
643
+ "{}{}record {}({}) {{",
644
+ indent, modifier, java_name, value_component
509
645
  );
510
- let _ = writeln!(out, "{} public {} {{", indent, java_name);
511
- for check in checks {
512
- let _ = writeln!(out, "{} {}", indent, check);
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
- let _ = writeln!(out, "{} }}", indent);
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, JavaCodegen, JavaOptions,
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
+ }
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "polyxml"
7
- version = "0.14.3"
7
+ version = "0.15.0"
8
8
  description = "High-performance, polyglot XML data-binding engine built in Rust"
9
9
  authors = [
10
10
  { name = "Bailey Nguyen", email = "bailey.tan.nguyen@gmail.com" }
File without changes