polyxml 0.16.0__tar.gz → 0.18.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.16.0 → polyxml-0.18.0}/Cargo.lock +5 -5
  2. {polyxml-0.16.0 → polyxml-0.18.0}/Cargo.toml +1 -1
  3. {polyxml-0.16.0 → polyxml-0.18.0}/PKG-INFO +1 -1
  4. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +80 -1
  5. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/codegen/go/mod.rs +67 -8
  6. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/codegen/mod.rs +2 -2
  7. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/codegen/rust/mod.rs +24 -0
  8. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +536 -36
  9. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +107 -0
  10. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_go_codegen.rs +130 -2
  11. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_rust_codegen.rs +77 -0
  12. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_ts_codegen.rs +243 -2
  13. {polyxml-0.16.0 → polyxml-0.18.0}/pyproject.toml +1 -1
  14. {polyxml-0.16.0 → polyxml-0.18.0}/README.md +0 -0
  15. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/Cargo.toml +0 -0
  16. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/README.md +0 -0
  17. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  18. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  19. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  20. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
  21. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/converters.rs +0 -0
  22. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/error.rs +0 -0
  23. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
  24. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  25. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/json.rs +0 -0
  26. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/lib.rs +0 -0
  27. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/parser.rs +0 -0
  28. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/schema.rs +0 -0
  29. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
  30. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/serializer.rs +0 -0
  31. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/transcoder.rs +0 -0
  32. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/src/value.rs +0 -0
  33. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  34. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_core.rs +0 -0
  35. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  36. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  37. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_json.rs +0 -0
  38. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  39. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  40. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  41. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_security.rs +0 -0
  42. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  43. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-python/Cargo.toml +0 -0
  44. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-python/README.md +0 -0
  45. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-python/src/lib.rs +0 -0
  46. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
  47. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  48. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-python/tests/test_json.py +0 -0
  49. {polyxml-0.16.0 → polyxml-0.18.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  50. {polyxml-0.16.0 → polyxml-0.18.0}/python/polyxml/AGENT_GUIDE.md +0 -0
  51. {polyxml-0.16.0 → polyxml-0.18.0}/python/polyxml/__init__.py +0 -0
  52. {polyxml-0.16.0 → polyxml-0.18.0}/python/polyxml/compat/__init__.py +0 -0
  53. {polyxml-0.16.0 → polyxml-0.18.0}/python/polyxml/compat/xsdata.py +0 -0
  54. {polyxml-0.16.0 → polyxml-0.18.0}/python/polyxml/py.typed +0 -0
@@ -549,7 +549,7 @@ dependencies = [
549
549
 
550
550
  [[package]]
551
551
  name = "polyxml"
552
- version = "0.16.0"
552
+ version = "0.18.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.16.0"
570
+ version = "0.18.0"
571
571
  dependencies = [
572
572
  "polyxml",
573
573
  ]
574
574
 
575
575
  [[package]]
576
576
  name = "polyxml-cli"
577
- version = "0.16.0"
577
+ version = "0.18.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.16.0"
591
+ version = "0.18.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.16.0"
601
+ version = "0.18.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.16.0"
6
+ version = "0.18.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.16.0
3
+ Version: 0.18.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -24,6 +24,16 @@ pub enum CSharpRecordKind {
24
24
  Struct,
25
25
  }
26
26
 
27
+ impl CSharpRecordKind {
28
+ pub fn from_str_loose(s: &str) -> Option<Self> {
29
+ match s.to_lowercase().trim() {
30
+ "class" | "record" | "record-class" | "reference" => Some(Self::Class),
31
+ "struct" | "record-struct" | "value" => Some(Self::Struct),
32
+ _ => None,
33
+ }
34
+ }
35
+ }
36
+
27
37
  /// Options configuring C# 12 / .NET 8+ code generation.
28
38
  #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29
39
  pub struct CSharpOptions {
@@ -33,6 +43,10 @@ pub struct CSharpOptions {
33
43
  pub emit_xml_attributes: bool,
34
44
  /// Emit System.Text.Json.Serialization attributes ([JsonPropertyName], etc.)
35
45
  pub emit_json_attributes: bool,
46
+ /// Emit System.Text.Json compile-time source generation context (JsonSerializerContext)
47
+ pub emit_source_gen: bool,
48
+ /// Name of the generated JsonSerializerContext partial class (default: "PolyXmlJsonContext")
49
+ pub source_gen_context_name: String,
36
50
  /// Emit IValidatableObject and restriction facet validation logic
37
51
  pub emit_validation: bool,
38
52
  /// Record emission kind (record class vs record struct)
@@ -49,6 +63,8 @@ impl Default for CSharpOptions {
49
63
  namespace: "Generated".to_string(),
50
64
  emit_xml_attributes: true,
51
65
  emit_json_attributes: true,
66
+ emit_source_gen: false,
67
+ source_gen_context_name: "PolyXmlJsonContext".to_string(),
52
68
  emit_validation: true,
53
69
  record_kind: CSharpRecordKind::Class,
54
70
  use_file_scoped_namespaces: true,
@@ -228,7 +244,7 @@ impl CSharpCodegen {
228
244
  if self.options.emit_xml_attributes {
229
245
  writeln!(out, "using System.Xml.Serialization;").unwrap();
230
246
  }
231
- if self.options.emit_json_attributes {
247
+ if self.options.emit_json_attributes || self.options.emit_source_gen {
232
248
  writeln!(out, "using System.Text.Json.Serialization;").unwrap();
233
249
  }
234
250
  writeln!(out).unwrap();
@@ -274,6 +290,11 @@ impl CSharpCodegen {
274
290
  self.emit_root_elements(&mut out, ir, indent);
275
291
  }
276
292
 
293
+ // Emit Source Generator Context if requested
294
+ if self.options.emit_source_gen {
295
+ self.emit_source_gen_context(&mut out, ir, indent);
296
+ }
297
+
277
298
  if !self.options.use_file_scoped_namespaces {
278
299
  writeln!(out, "}}").unwrap();
279
300
  }
@@ -288,6 +309,64 @@ impl CSharpCodegen {
288
309
  vec![(filename, code)]
289
310
  }
290
311
 
312
+ fn emit_source_gen_context(&self, out: &mut String, ir: &SchemaIR, indent: &str) {
313
+ writeln!(out).unwrap();
314
+ writeln!(
315
+ out,
316
+ "{}/// <summary>\n{}/// Source-generated JsonSerializerContext for Native AOT and high-throughput serialization.\n{}/// </summary>",
317
+ indent, indent, indent
318
+ )
319
+ .unwrap();
320
+ writeln!(
321
+ out,
322
+ "{}[JsonSourceGenerationOptions(WriteIndented = true)]",
323
+ indent
324
+ )
325
+ .unwrap();
326
+
327
+ // Enums
328
+ for def in ir.types.values() {
329
+ if let TypeDef::Enum(e) = def {
330
+ let name = to_csharp_type_name(&e.qname.local);
331
+ writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
332
+ }
333
+ }
334
+
335
+ // Simple types
336
+ for def in ir.types.values() {
337
+ if let TypeDef::Simple(s) = def {
338
+ let name = to_csharp_type_name(&s.qname.local);
339
+ writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
340
+ }
341
+ }
342
+
343
+ // Unions
344
+ for def in ir.types.values() {
345
+ if let TypeDef::Union(u) = def {
346
+ let name = to_csharp_type_name(&u.qname.local);
347
+ writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
348
+ }
349
+ }
350
+
351
+ // Structs
352
+ for def in ir.types.values() {
353
+ if let TypeDef::Struct(s) = def {
354
+ let name = to_csharp_type_name(&s.qname.local);
355
+ writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
356
+ writeln!(out, "{}[JsonSerializable(typeof(List<{}>))]", indent, name).unwrap();
357
+ }
358
+ }
359
+
360
+ let ctx_name = &self.options.source_gen_context_name;
361
+ writeln!(
362
+ out,
363
+ "{}public partial class {} : JsonSerializerContext\n{}{{",
364
+ indent, ctx_name, indent
365
+ )
366
+ .unwrap();
367
+ writeln!(out, "{}}}", indent).unwrap();
368
+ }
369
+
291
370
  fn emit_enum(&self, out: &mut String, e: &EnumDef, indent: &str) {
292
371
  let enum_name = to_csharp_type_name(&e.qname.local);
293
372
  if let Some(ref doc) = e.documentation {
@@ -9,9 +9,34 @@ use crate::ir::{
9
9
  StructDef, TypeDef, TypeRef, UnionDef,
10
10
  };
11
11
 
12
+ /// Target serialization backend for Go codegen.
13
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
14
+ pub enum GoBackend {
15
+ /// Standard library `encoding/json` and `encoding/xml` (default)
16
+ #[default]
17
+ Standard,
18
+ /// Mailru/EasyJSON static reflectionless serialization generator (`//easyjson:json`)
19
+ EasyJson,
20
+ /// ByteDance Sonic high-performance JIT serialization (`sonic:"..."` struct tags)
21
+ Sonic,
22
+ }
23
+
24
+ impl GoBackend {
25
+ pub fn from_str_loose(s: &str) -> Option<Self> {
26
+ match s.to_lowercase().trim() {
27
+ "standard" | "std" | "default" => Some(Self::Standard),
28
+ "easyjson" | "easy-json" | "easy_json" => Some(Self::EasyJson),
29
+ "sonic" | "bytedance" => Some(Self::Sonic),
30
+ _ => None,
31
+ }
32
+ }
33
+ }
34
+
12
35
  /// Options configuring Go 1.22+ code generation.
13
36
  #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14
37
  pub struct GoOptions {
38
+ /// Target serialization backend (default: GoBackend::Standard)
39
+ pub backend: GoBackend,
15
40
  /// Package name for generated Go code (default: "models")
16
41
  pub package_name: String,
17
42
  /// Emit encoding/xml tags (default: true)
@@ -29,6 +54,7 @@ pub struct GoOptions {
29
54
  impl Default for GoOptions {
30
55
  fn default() -> Self {
31
56
  Self {
57
+ backend: GoBackend::Standard,
32
58
  package_name: "models".to_string(),
33
59
  emit_xml_tags: true,
34
60
  emit_json_tags: true,
@@ -407,6 +433,10 @@ impl GoCodegen {
407
433
  }
408
434
  }
409
435
 
436
+ if self.options.backend == GoBackend::EasyJson {
437
+ writeln!(out, "//easyjson:json").unwrap();
438
+ }
439
+
410
440
  // Choice container struct
411
441
  writeln!(out, "type {} struct {{", choice_name).unwrap();
412
442
  for branch in &u.branches {
@@ -415,12 +445,22 @@ impl GoCodegen {
415
445
  }
416
446
  let field_name = to_go_field_name(&branch.variant_name);
417
447
  let mapped_type = self.context.map_type_ref(&branch.type_ref);
418
- let xml_tag = if self.options.emit_xml_tags {
419
- format!(" `xml:\"{},omitempty\"`", branch.xml_name)
420
- } else {
448
+ let mut tag_parts = Vec::new();
449
+ if self.options.emit_xml_tags {
450
+ tag_parts.push(format!("xml:\"{},omitempty\"", branch.xml_name));
451
+ }
452
+ if self.options.emit_json_tags {
453
+ tag_parts.push(format!("json:\"{},omitempty\"", branch.xml_name));
454
+ }
455
+ if self.options.backend == GoBackend::Sonic {
456
+ tag_parts.push(format!("sonic:\"{},omitempty\"", branch.xml_name));
457
+ }
458
+ let tag = if tag_parts.is_empty() {
421
459
  String::new()
460
+ } else {
461
+ format!(" `{}`", tag_parts.join(" "))
422
462
  };
423
- writeln!(out, " {} *{}{}", field_name, mapped_type, xml_tag).unwrap();
463
+ writeln!(out, " {} *{}{}", field_name, mapped_type, tag).unwrap();
424
464
  }
425
465
  writeln!(out, "}}\n").unwrap();
426
466
 
@@ -539,14 +579,25 @@ impl GoCodegen {
539
579
  }
540
580
  }
541
581
 
582
+ if self.options.backend == GoBackend::EasyJson {
583
+ writeln!(out, "//easyjson:json").unwrap();
584
+ }
585
+
542
586
  writeln!(out, "type {} struct {{", struct_name).unwrap();
543
587
 
544
588
  // Emit XMLName if xml tags enabled
545
589
  if self.options.emit_xml_tags {
590
+ let mut xml_tags = Vec::new();
546
591
  if self.options.emit_json_tags {
547
- writeln!(out, " XMLName xml.Name `json:\"-\"`").unwrap();
548
- } else {
592
+ xml_tags.push("json:\"-\"");
593
+ }
594
+ if self.options.backend == GoBackend::Sonic {
595
+ xml_tags.push("sonic:\"-\"");
596
+ }
597
+ if xml_tags.is_empty() {
549
598
  writeln!(out, " XMLName xml.Name").unwrap();
599
+ } else {
600
+ writeln!(out, " XMLName xml.Name `{}`", xml_tags.join(" ")).unwrap();
550
601
  }
551
602
  }
552
603
 
@@ -576,6 +627,9 @@ impl GoCodegen {
576
627
  if self.options.emit_json_tags {
577
628
  parts.push("json:\"value,omitempty\"".to_string());
578
629
  }
630
+ if self.options.backend == GoBackend::Sonic {
631
+ parts.push("sonic:\"value,omitempty\"".to_string());
632
+ }
579
633
  let tag = if parts.is_empty() {
580
634
  String::new()
581
635
  } else {
@@ -647,7 +701,7 @@ impl GoCodegen {
647
701
  parts.push(format!("xml:\"{}\"", xml_val));
648
702
  }
649
703
 
650
- if self.options.emit_json_tags {
704
+ if self.options.emit_json_tags || self.options.backend == GoBackend::Sonic {
651
705
  let is_opt = f.cardinality.is_optional() || f.nillable;
652
706
  let json_name = if f.kind == FieldKind::Text {
653
707
  "value".to_string()
@@ -659,7 +713,12 @@ impl GoCodegen {
659
713
  } else {
660
714
  json_name
661
715
  };
662
- parts.push(format!("json:\"{}\"", json_val));
716
+ if self.options.emit_json_tags {
717
+ parts.push(format!("json:\"{}\"", json_val));
718
+ }
719
+ if self.options.backend == GoBackend::Sonic {
720
+ parts.push(format!("sonic:\"{}\"", json_val));
721
+ }
663
722
  }
664
723
 
665
724
  if parts.is_empty() {
@@ -15,11 +15,11 @@ pub mod typescript;
15
15
 
16
16
  pub use cpp::{CppBackend, CppCodegen, CppMode, CppOptions};
17
17
  pub use csharp::{CSharpCodegen, CSharpOptions, CSharpRecordKind};
18
- pub use go::{GoCodegen, GoOptions};
18
+ pub use go::{GoBackend, GoCodegen, GoOptions};
19
19
  pub use java::{JavaBackend, JavaCodegen, JavaOptions};
20
20
  pub use python::{PythonBackend, PythonCodegen, PythonOptions};
21
21
  pub use rust::{RustCodegen, RustOptions};
22
- pub use typescript::{TypeScriptCodegen, TypeScriptOptions};
22
+ pub use typescript::{TypeScriptBackend, TypeScriptCodegen, TypeScriptOptions};
23
23
 
24
24
  use crate::ir::{PrimitiveType, TypeRef};
25
25
 
@@ -25,6 +25,8 @@ pub struct RustOptions {
25
25
  pub emit_root_aliases: bool,
26
26
  /// Emit streaming XML codecs (from_xml, to_xml, decode_xml, encode_xml) (default: true).
27
27
  pub emit_codecs: bool,
28
+ /// Derive rkyv::{Archive, Serialize, Deserialize} zero-copy wire format serialization (default: false).
29
+ pub emit_rkyv: bool,
28
30
  }
29
31
 
30
32
  impl Default for RustOptions {
@@ -36,6 +38,7 @@ impl Default for RustOptions {
36
38
  emit_polyxml_attrs: false,
37
39
  emit_root_aliases: true,
38
40
  emit_codecs: true,
41
+ emit_rkyv: false,
39
42
  }
40
43
  }
41
44
  }
@@ -431,6 +434,13 @@ impl RustCodegen {
431
434
  }
432
435
 
433
436
  let _ = writeln!(out, "#[derive({})]", derives.join(", "));
437
+ if self.options.emit_rkyv {
438
+ let _ = writeln!(
439
+ out,
440
+ "#[cfg_attr(feature = \"rkyv\", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]"
441
+ );
442
+ let _ = writeln!(out, "#[cfg_attr(feature = \"rkyv\", rkyv(check_bytes))]");
443
+ }
434
444
  let _ = writeln!(out, "pub enum {} {{", enum_name);
435
445
 
436
446
  let mut seen_variants = HashSet::new();
@@ -520,6 +530,13 @@ impl RustCodegen {
520
530
  }
521
531
 
522
532
  let _ = writeln!(out, "#[derive({})]", derives.join(", "));
533
+ if self.options.emit_rkyv {
534
+ let _ = writeln!(
535
+ out,
536
+ "#[cfg_attr(feature = \"rkyv\", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]"
537
+ );
538
+ let _ = writeln!(out, "#[cfg_attr(feature = \"rkyv\", rkyv(check_bytes))]");
539
+ }
523
540
  let type_signature = if needs_lifetime {
524
541
  format!("{}<'a>", union_name)
525
542
  } else {
@@ -587,6 +604,13 @@ impl RustCodegen {
587
604
  }
588
605
 
589
606
  let _ = writeln!(out, "#[derive({})]", derives.join(", "));
607
+ if self.options.emit_rkyv {
608
+ let _ = writeln!(
609
+ out,
610
+ "#[cfg_attr(feature = \"rkyv\", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]"
611
+ );
612
+ let _ = writeln!(out, "#[cfg_attr(feature = \"rkyv\", rkyv(check_bytes))]");
613
+ }
590
614
 
591
615
  let struct_decl = if needs_lifetime {
592
616
  format!("pub struct {}<'a>", struct_name)