polyxml 0.14.3__tar.gz → 0.16.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.16.0}/Cargo.lock +5 -5
- {polyxml-0.14.3 → polyxml-0.16.0}/Cargo.toml +1 -1
- {polyxml-0.14.3 → polyxml-0.16.0}/PKG-INFO +1 -1
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +156 -4
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/codegen/java/mod.rs +164 -12
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/codegen/mod.rs +2 -2
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +140 -2
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_java_codegen.rs +280 -1
- {polyxml-0.14.3 → polyxml-0.16.0}/pyproject.toml +1 -1
- {polyxml-0.14.3 → polyxml-0.16.0}/README.md +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/Cargo.toml +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/README.md +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/converters.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/error.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/json.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/lib.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/parser.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/schema.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/serializer.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/transcoder.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/src/value.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_core.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_json.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_security.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-python/Cargo.toml +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-python/README.md +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-python/src/lib.rs +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-python/tests/test_json.py +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/python/polyxml/AGENT_GUIDE.md +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/python/polyxml/__init__.py +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/python/polyxml/compat/__init__.py +0 -0
- {polyxml-0.14.3 → polyxml-0.16.0}/python/polyxml/compat/xsdata.py +0 -0
- {polyxml-0.14.3 → polyxml-0.16.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.16.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.16.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.16.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.16.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.16.0"
|
|
602
602
|
dependencies = [
|
|
603
603
|
"lexical-core",
|
|
604
604
|
"polyxml",
|
|
@@ -20,6 +20,40 @@ pub enum CppMode {
|
|
|
20
20
|
Module,
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
impl CppMode {
|
|
24
|
+
pub fn from_str_loose(s: &str) -> Option<Self> {
|
|
25
|
+
match s.to_lowercase().trim() {
|
|
26
|
+
"header" | "headeronly" | "header-only" | "headers" | "hpp" => Some(Self::HeaderOnly),
|
|
27
|
+
"module" | "modules" | "cppm" | "c++20-modules" => Some(Self::Module),
|
|
28
|
+
_ => None,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Target backend for C++ serialization/reflection metadata.
|
|
34
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
|
35
|
+
pub enum CppBackend {
|
|
36
|
+
/// Zero-dependency standard C++20 (default)
|
|
37
|
+
#[default]
|
|
38
|
+
Standard,
|
|
39
|
+
/// Glaze compile-time reflectionless serde (`glz::meta`)
|
|
40
|
+
Glaze,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
impl CppBackend {
|
|
44
|
+
pub fn from_str_loose(s: &str) -> Option<Self> {
|
|
45
|
+
match s.to_lowercase().trim() {
|
|
46
|
+
"standard" | "std" | "default" | "none" => Some(Self::Standard),
|
|
47
|
+
"glaze" | "glz" => Some(Self::Glaze),
|
|
48
|
+
_ => None,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
pub fn is_glaze(self) -> bool {
|
|
53
|
+
matches!(self, Self::Glaze)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
23
57
|
/// Options configuring modern C++20/C++23 code generation.
|
|
24
58
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
25
59
|
pub struct CppOptions {
|
|
@@ -27,6 +61,8 @@ pub struct CppOptions {
|
|
|
27
61
|
pub namespace: String,
|
|
28
62
|
/// Header-only (.hpp) or C++20 Module (.cppm) mode (default: HeaderOnly)
|
|
29
63
|
pub mode: CppMode,
|
|
64
|
+
/// Target serialization and metadata backend (default: Standard)
|
|
65
|
+
pub backend: CppBackend,
|
|
30
66
|
/// Standard language dialect (default: "c++20")
|
|
31
67
|
pub standard: String,
|
|
32
68
|
/// Emit C++20 defaulted equality operators `bool operator==(const T&) const = default;` (default: true)
|
|
@@ -48,6 +84,7 @@ impl Default for CppOptions {
|
|
|
48
84
|
Self {
|
|
49
85
|
namespace: "polyxml::generated".to_string(),
|
|
50
86
|
mode: CppMode::HeaderOnly,
|
|
87
|
+
backend: CppBackend::Standard,
|
|
51
88
|
standard: "c++20".to_string(),
|
|
52
89
|
emit_equality_operators: true,
|
|
53
90
|
emit_enum_converters: true,
|
|
@@ -229,6 +266,11 @@ impl CppCodegen {
|
|
|
229
266
|
self.emit_root_aliases(&mut out, ir);
|
|
230
267
|
|
|
231
268
|
writeln!(out, "\n}} // namespace {}", ns).unwrap();
|
|
269
|
+
|
|
270
|
+
if self.options.backend.is_glaze() {
|
|
271
|
+
self.emit_glaze_meta(&mut out, ir);
|
|
272
|
+
}
|
|
273
|
+
|
|
232
274
|
out
|
|
233
275
|
}
|
|
234
276
|
|
|
@@ -261,6 +303,11 @@ impl CppCodegen {
|
|
|
261
303
|
self.emit_root_aliases(&mut out, ir);
|
|
262
304
|
|
|
263
305
|
writeln!(out, "\n}} // namespace {}", ns).unwrap();
|
|
306
|
+
|
|
307
|
+
if self.options.backend.is_glaze() {
|
|
308
|
+
self.emit_glaze_meta(&mut out, ir);
|
|
309
|
+
}
|
|
310
|
+
|
|
264
311
|
out
|
|
265
312
|
}
|
|
266
313
|
|
|
@@ -314,8 +361,26 @@ impl CppCodegen {
|
|
|
314
361
|
/// Generate CMakeLists.txt definition for integration via add_subdirectory or FetchContent.
|
|
315
362
|
pub fn generate_cmake(&self, project_name: &str) -> String {
|
|
316
363
|
let safe_name = AsSnakeCase(project_name).to_string();
|
|
317
|
-
|
|
318
|
-
|
|
364
|
+
if self.options.mode == CppMode::Module {
|
|
365
|
+
format!(
|
|
366
|
+
r#"cmake_minimum_required(VERSION 3.28)
|
|
367
|
+
project({safe_name}_models LANGUAGES CXX)
|
|
368
|
+
|
|
369
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
370
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
371
|
+
|
|
372
|
+
add_library({safe_name})
|
|
373
|
+
target_sources({safe_name}
|
|
374
|
+
PUBLIC
|
|
375
|
+
FILE_SET CXX_MODULES FILES
|
|
376
|
+
{safe_name}.cppm
|
|
377
|
+
)
|
|
378
|
+
target_compile_features({safe_name} PUBLIC cxx_std_20)
|
|
379
|
+
"#
|
|
380
|
+
)
|
|
381
|
+
} else {
|
|
382
|
+
format!(
|
|
383
|
+
r#"cmake_minimum_required(VERSION 3.20)
|
|
319
384
|
project({safe_name}_models LANGUAGES CXX)
|
|
320
385
|
|
|
321
386
|
set(CMAKE_CXX_STANDARD 20)
|
|
@@ -328,7 +393,8 @@ target_include_directories({safe_name} INTERFACE
|
|
|
328
393
|
)
|
|
329
394
|
target_compile_features({safe_name} INTERFACE cxx_std_20)
|
|
330
395
|
"#
|
|
331
|
-
|
|
396
|
+
)
|
|
397
|
+
}
|
|
332
398
|
}
|
|
333
399
|
|
|
334
400
|
/// Generate standalone PolyXMLConfig.cmake for `find_package(PolyXML)`.
|
|
@@ -372,7 +438,93 @@ endif()
|
|
|
372
438
|
writeln!(out, "#include <string>").unwrap();
|
|
373
439
|
writeln!(out, "#include <string_view>").unwrap();
|
|
374
440
|
writeln!(out, "#include <variant>").unwrap();
|
|
375
|
-
writeln!(out, "#include <vector
|
|
441
|
+
writeln!(out, "#include <vector>").unwrap();
|
|
442
|
+
|
|
443
|
+
if self.options.backend.is_glaze() {
|
|
444
|
+
writeln!(out, "#include <glaze/glaze.hpp>").unwrap();
|
|
445
|
+
}
|
|
446
|
+
writeln!(out).unwrap();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
fn emit_glaze_meta(&self, out: &mut String, ir: &SchemaIR) {
|
|
450
|
+
let ns = to_cpp_namespace(&self.options.namespace);
|
|
451
|
+
writeln!(out, "\n// Glaze compile-time reflection metadata\n").unwrap();
|
|
452
|
+
|
|
453
|
+
// 1. Enums
|
|
454
|
+
for type_def in ir.types.values() {
|
|
455
|
+
if let TypeDef::Enum(enum_def) = type_def {
|
|
456
|
+
let enum_name = to_cpp_type_name(&enum_def.qname.local);
|
|
457
|
+
let full_type = format!("{}::{}", ns, enum_name);
|
|
458
|
+
writeln!(out, "template <>").unwrap();
|
|
459
|
+
writeln!(out, "struct glz::meta<{}> {{", full_type).unwrap();
|
|
460
|
+
writeln!(out, " using T = {};", full_type).unwrap();
|
|
461
|
+
if enum_def.variants.is_empty() {
|
|
462
|
+
writeln!(out, " static constexpr auto value = enumerate();").unwrap();
|
|
463
|
+
} else {
|
|
464
|
+
writeln!(out, " static constexpr auto value = enumerate(").unwrap();
|
|
465
|
+
for (i, v) in enum_def.variants.iter().enumerate() {
|
|
466
|
+
let var_name = to_cpp_enum_variant(&v.name);
|
|
467
|
+
let comma = if i + 1 < enum_def.variants.len() {
|
|
468
|
+
","
|
|
469
|
+
} else {
|
|
470
|
+
""
|
|
471
|
+
};
|
|
472
|
+
writeln!(out, " {:?}, T::{}{}", v.value, var_name, comma).unwrap();
|
|
473
|
+
}
|
|
474
|
+
writeln!(out, " );").unwrap();
|
|
475
|
+
}
|
|
476
|
+
writeln!(out, "}};\n").unwrap();
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// 2. Structs
|
|
481
|
+
let sorted_qnames = self.topological_sort_types(ir);
|
|
482
|
+
for qname in sorted_qnames {
|
|
483
|
+
if let Some(TypeDef::Struct(s)) = ir.types.get(&qname) {
|
|
484
|
+
let struct_name = to_cpp_type_name(&s.qname.local);
|
|
485
|
+
let full_type = format!("{}::{}", ns, struct_name);
|
|
486
|
+
writeln!(out, "template <>").unwrap();
|
|
487
|
+
writeln!(out, "struct glz::meta<{}> {{", full_type).unwrap();
|
|
488
|
+
writeln!(out, " using T = {};", full_type).unwrap();
|
|
489
|
+
|
|
490
|
+
let mut field_bindings = Vec::new();
|
|
491
|
+
|
|
492
|
+
// Check for simple_content_base "value"
|
|
493
|
+
if let Some(ref base_qname) = s.base_type {
|
|
494
|
+
let has_value_field = s
|
|
495
|
+
.fields
|
|
496
|
+
.iter()
|
|
497
|
+
.any(|f| f.name == "value" || f.kind == crate::ir::FieldKind::Text);
|
|
498
|
+
if !has_value_field
|
|
499
|
+
&& (PrimitiveType::from_xsd_name(&base_qname.local).is_some()
|
|
500
|
+
|| matches!(ir.types.get(base_qname), Some(TypeDef::Simple(_))))
|
|
501
|
+
{
|
|
502
|
+
field_bindings.push(r#""value", &T::value"#.to_string());
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
for f in &s.fields {
|
|
507
|
+
let field_name = to_cpp_field_name(&f.name);
|
|
508
|
+
field_bindings.push(format!("{:?}, &T::{}", f.xml_name, field_name));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if field_bindings.is_empty() {
|
|
512
|
+
writeln!(out, " static constexpr auto value = object();").unwrap();
|
|
513
|
+
} else {
|
|
514
|
+
writeln!(out, " static constexpr auto value = object(").unwrap();
|
|
515
|
+
for (i, fb) in field_bindings.iter().enumerate() {
|
|
516
|
+
let comma = if i + 1 < field_bindings.len() {
|
|
517
|
+
","
|
|
518
|
+
} else {
|
|
519
|
+
""
|
|
520
|
+
};
|
|
521
|
+
writeln!(out, " {}{}", fb, comma).unwrap();
|
|
522
|
+
}
|
|
523
|
+
writeln!(out, " );").unwrap();
|
|
524
|
+
}
|
|
525
|
+
writeln!(out, "}};\n").unwrap();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
376
528
|
}
|
|
377
529
|
|
|
378
530
|
fn emit_utilities(&self, out: &mut String) {
|
|
@@ -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
|
|
|
@@ -13,10 +13,10 @@ pub mod python;
|
|
|
13
13
|
pub mod rust;
|
|
14
14
|
pub mod typescript;
|
|
15
15
|
|
|
16
|
-
pub use cpp::{CppCodegen, CppMode, CppOptions};
|
|
16
|
+
pub use cpp::{CppBackend, 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,8 +3,8 @@ use std::process::Command;
|
|
|
3
3
|
use tempfile::tempdir;
|
|
4
4
|
|
|
5
5
|
use polyxml::codegen::cpp::{
|
|
6
|
-
to_cpp_enum_variant, to_cpp_field_name, to_cpp_namespace, to_cpp_type_name,
|
|
7
|
-
CppMode, CppOptions,
|
|
6
|
+
to_cpp_enum_variant, to_cpp_field_name, to_cpp_namespace, to_cpp_type_name, CppBackend,
|
|
7
|
+
CppCodegen, CppMode, CppOptions,
|
|
8
8
|
};
|
|
9
9
|
use polyxml::ir::{
|
|
10
10
|
Cardinality, EnumDef, EnumValue, FieldDef, FieldKind, PrimitiveType, QName, RestrictionFacets,
|
|
@@ -154,6 +154,7 @@ fn test_cpp_struct_and_enum_codegen_compilation() {
|
|
|
154
154
|
let options = CppOptions {
|
|
155
155
|
namespace: "crm::models".to_string(),
|
|
156
156
|
mode: CppMode::HeaderOnly,
|
|
157
|
+
backend: CppBackend::Standard,
|
|
157
158
|
standard: "c++20".to_string(),
|
|
158
159
|
emit_equality_operators: true,
|
|
159
160
|
emit_enum_converters: true,
|
|
@@ -862,3 +863,140 @@ int main() {
|
|
|
862
863
|
"ISO 20022 test binary execution failed"
|
|
863
864
|
);
|
|
864
865
|
}
|
|
866
|
+
|
|
867
|
+
#[test]
|
|
868
|
+
fn test_cpp_mode_and_backend_from_str_loose() {
|
|
869
|
+
assert_eq!(CppMode::from_str_loose("header"), Some(CppMode::HeaderOnly));
|
|
870
|
+
assert_eq!(
|
|
871
|
+
CppMode::from_str_loose("header-only"),
|
|
872
|
+
Some(CppMode::HeaderOnly)
|
|
873
|
+
);
|
|
874
|
+
assert_eq!(CppMode::from_str_loose("hpp"), Some(CppMode::HeaderOnly));
|
|
875
|
+
assert_eq!(CppMode::from_str_loose("modules"), Some(CppMode::Module));
|
|
876
|
+
assert_eq!(CppMode::from_str_loose("module"), Some(CppMode::Module));
|
|
877
|
+
assert_eq!(CppMode::from_str_loose("cppm"), Some(CppMode::Module));
|
|
878
|
+
assert_eq!(CppMode::from_str_loose("unknown"), None);
|
|
879
|
+
|
|
880
|
+
assert_eq!(
|
|
881
|
+
CppBackend::from_str_loose("standard"),
|
|
882
|
+
Some(CppBackend::Standard)
|
|
883
|
+
);
|
|
884
|
+
assert_eq!(
|
|
885
|
+
CppBackend::from_str_loose("default"),
|
|
886
|
+
Some(CppBackend::Standard)
|
|
887
|
+
);
|
|
888
|
+
assert_eq!(CppBackend::from_str_loose("glaze"), Some(CppBackend::Glaze));
|
|
889
|
+
assert_eq!(CppBackend::from_str_loose("glz"), Some(CppBackend::Glaze));
|
|
890
|
+
assert_eq!(CppBackend::from_str_loose("unknown"), None);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
#[test]
|
|
894
|
+
fn test_cpp_glaze_backend_meta_generation() {
|
|
895
|
+
let mut ir = SchemaIR::new().with_target_namespace("https://example.com/shop");
|
|
896
|
+
|
|
897
|
+
// Enum
|
|
898
|
+
ir.add_type(TypeDef::Enum(EnumDef {
|
|
899
|
+
qname: QName::new(Some("https://example.com/shop"), "OrderStatus"),
|
|
900
|
+
base_type: TypeRef::Primitive(PrimitiveType::String),
|
|
901
|
+
variants: vec![
|
|
902
|
+
EnumValue {
|
|
903
|
+
name: "pending".into(),
|
|
904
|
+
value: "pending".into(),
|
|
905
|
+
documentation: None,
|
|
906
|
+
},
|
|
907
|
+
EnumValue {
|
|
908
|
+
name: "shipped".into(),
|
|
909
|
+
value: "shipped".into(),
|
|
910
|
+
documentation: None,
|
|
911
|
+
},
|
|
912
|
+
],
|
|
913
|
+
documentation: None,
|
|
914
|
+
}));
|
|
915
|
+
|
|
916
|
+
// Struct
|
|
917
|
+
ir.add_type(TypeDef::Struct(StructDef {
|
|
918
|
+
qname: QName::new(Some("https://example.com/shop"), "Order"),
|
|
919
|
+
base_type: None,
|
|
920
|
+
is_abstract: false,
|
|
921
|
+
fields: vec![
|
|
922
|
+
FieldDef {
|
|
923
|
+
name: "order_id".into(),
|
|
924
|
+
xml_name: "orderId".into(),
|
|
925
|
+
namespace: None,
|
|
926
|
+
kind: FieldKind::Element,
|
|
927
|
+
type_ref: TypeRef::Primitive(PrimitiveType::Int),
|
|
928
|
+
cardinality: Cardinality::required_one(),
|
|
929
|
+
nillable: false,
|
|
930
|
+
default_value: None,
|
|
931
|
+
fixed_value: None,
|
|
932
|
+
documentation: None,
|
|
933
|
+
facets: None,
|
|
934
|
+
is_cycle_cut: false,
|
|
935
|
+
},
|
|
936
|
+
FieldDef {
|
|
937
|
+
name: "status".into(),
|
|
938
|
+
xml_name: "status".into(),
|
|
939
|
+
namespace: None,
|
|
940
|
+
kind: FieldKind::Element,
|
|
941
|
+
type_ref: TypeRef::Named(QName::new(
|
|
942
|
+
Some("https://example.com/shop"),
|
|
943
|
+
"OrderStatus",
|
|
944
|
+
)),
|
|
945
|
+
cardinality: Cardinality::required_one(),
|
|
946
|
+
nillable: false,
|
|
947
|
+
default_value: None,
|
|
948
|
+
fixed_value: None,
|
|
949
|
+
documentation: None,
|
|
950
|
+
facets: None,
|
|
951
|
+
is_cycle_cut: false,
|
|
952
|
+
},
|
|
953
|
+
],
|
|
954
|
+
documentation: None,
|
|
955
|
+
}));
|
|
956
|
+
|
|
957
|
+
let options = CppOptions {
|
|
958
|
+
namespace: "shop".to_string(),
|
|
959
|
+
backend: CppBackend::Glaze,
|
|
960
|
+
..Default::default()
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
let codegen = CppCodegen::new(options);
|
|
964
|
+
let header_code = codegen.generate_header(&ir);
|
|
965
|
+
|
|
966
|
+
// Includes Glaze header
|
|
967
|
+
assert!(header_code.contains("#include <glaze/glaze.hpp>"));
|
|
968
|
+
|
|
969
|
+
// Glaze enum reflection
|
|
970
|
+
assert!(header_code.contains("template <>"));
|
|
971
|
+
assert!(header_code.contains("struct glz::meta<shop::OrderStatus> {"));
|
|
972
|
+
assert!(header_code.contains("using T = shop::OrderStatus;"));
|
|
973
|
+
assert!(header_code.contains("static constexpr auto value = enumerate("));
|
|
974
|
+
assert!(header_code.contains("\"pending\", T::Pending,"));
|
|
975
|
+
assert!(header_code.contains("\"shipped\", T::Shipped"));
|
|
976
|
+
|
|
977
|
+
// Glaze struct reflection
|
|
978
|
+
assert!(header_code.contains("struct glz::meta<shop::Order> {"));
|
|
979
|
+
assert!(header_code.contains("using T = shop::Order;"));
|
|
980
|
+
assert!(header_code.contains("static constexpr auto value = object("));
|
|
981
|
+
assert!(header_code.contains("\"orderId\", &T::order_id,"));
|
|
982
|
+
assert!(header_code.contains("\"status\", &T::status"));
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
#[test]
|
|
986
|
+
fn test_cpp_cmake_modules_file_set() {
|
|
987
|
+
let _ir = SchemaIR::new();
|
|
988
|
+
|
|
989
|
+
let options = CppOptions {
|
|
990
|
+
namespace: "telemetry".to_string(),
|
|
991
|
+
mode: CppMode::Module,
|
|
992
|
+
emit_cmake: true,
|
|
993
|
+
..Default::default()
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
let codegen = CppCodegen::new(options);
|
|
997
|
+
let cmake = codegen.generate_cmake("telemetry");
|
|
998
|
+
|
|
999
|
+
assert!(cmake.contains("cmake_minimum_required(VERSION 3.28)"));
|
|
1000
|
+
assert!(cmake.contains("FILE_SET CXX_MODULES FILES"));
|
|
1001
|
+
assert!(cmake.contains("telemetry.cppm"));
|
|
1002
|
+
}
|
|
@@ -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
|