lutaml-model 0.8.56 → 0.8.106

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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/.github/scripts/test_installed_gem.rb +58 -0
  3. data/.github/workflows/downstream-performance.yml +28 -3
  4. data/.github/workflows/js-pr-check.yml +4 -0
  5. data/.github/workflows/js-sync.yml +4 -0
  6. data/.github/workflows/opal.yml +9 -0
  7. data/.github/workflows/rake.yml +6 -41
  8. data/.github/workflows/release.yml +41 -0
  9. data/Gemfile +17 -3
  10. data/bench/bench_alloc_audit.rb +56 -0
  11. data/bench/bench_common.rb +11 -0
  12. data/bench/bench_merge_attempts.rb +49 -0
  13. data/docs/_guides/gem-integration.adoc +85 -0
  14. data/docs/_guides/grammar-backed-formats.adoc +69 -0
  15. data/docs/_guides/index.adoc +8 -1
  16. data/docs/_guides/native-engines.adoc +23 -5
  17. data/docs/_guides/schema-generation.adoc +25 -0
  18. data/docs/_guides/type-registers-contexts.adoc +126 -0
  19. data/lib/compat/opal/lutaml_model_boot.rb +7 -5
  20. data/lib/lutaml/json/adapter/yeptris_adapter.rb +24 -1
  21. data/lib/lutaml/key_value/data_model/element.rb +5 -1
  22. data/lib/lutaml/key_value/mapping_rule.rb +7 -0
  23. data/lib/lutaml/key_value/transform.rb +29 -24
  24. data/lib/lutaml/key_value/transformation/collection_serializer.rb +12 -29
  25. data/lib/lutaml/key_value/transformation/value_serializer.rb +29 -6
  26. data/lib/lutaml/key_value/transformation.rb +64 -116
  27. data/lib/lutaml/model/adapter_resolver.rb +7 -3
  28. data/lib/lutaml/model/attribute.rb +74 -15
  29. data/lib/lutaml/model/cached_type_resolver.rb +13 -6
  30. data/lib/lutaml/model/configuration.rb +13 -0
  31. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  32. data/lib/lutaml/model/mapping/mapping_rule.rb +6 -1
  33. data/lib/lutaml/model/render_policy.rb +26 -8
  34. data/lib/lutaml/model/schema/base_schema.rb +15 -0
  35. data/lib/lutaml/model/schema/generator/definition.rb +52 -8
  36. data/lib/lutaml/model/schema/generator/definitions_collection.rb +35 -14
  37. data/lib/lutaml/model/schema.rb +4 -2
  38. data/lib/lutaml/model/serializable.rb +33 -0
  39. data/lib/lutaml/model/serialize/attribute_definition.rb +196 -2
  40. data/lib/lutaml/model/serialize/builder.rb +22 -0
  41. data/lib/lutaml/model/serialize/conversion_caching.rb +5 -0
  42. data/lib/lutaml/model/serialize/format_conversion.rb +49 -41
  43. data/lib/lutaml/model/serialize/initialization.rb +54 -7
  44. data/lib/lutaml/model/serialize/model_import.rb +60 -0
  45. data/lib/lutaml/model/serialize.rb +14 -6
  46. data/lib/lutaml/model/type/time.rb +6 -0
  47. data/lib/lutaml/model/type/value.rb +9 -7
  48. data/lib/lutaml/model/type_resolver.rb +7 -5
  49. data/lib/lutaml/model/utils.rb +18 -5
  50. data/lib/lutaml/model/version.rb +1 -1
  51. data/lib/lutaml/model.rb +7 -0
  52. data/lib/lutaml/xml/adapter/xml_parser.rb +5 -2
  53. data/lib/lutaml/xml/adapter_loader.rb +5 -2
  54. data/lib/lutaml/xml/format.rb +18 -16
  55. data/lib/lutaml/xml/leptris/native_emission.rb +187 -0
  56. data/lib/lutaml/xml/leptris/plan_compiler.rb +650 -0
  57. data/lib/lutaml/xml/leptris/plan_hydrator.rb +748 -0
  58. data/lib/lutaml/xml/leptris/plan_order.rb +55 -0
  59. data/lib/lutaml/xml/leptris/plan_serializer.rb +702 -0
  60. data/lib/lutaml/xml/leptris/plan_walk.rb +36 -0
  61. data/lib/lutaml/xml/leptris.rb +46 -0
  62. data/lib/lutaml/xml/mapping.rb +71 -18
  63. data/lib/lutaml/xml/model_transform.rb +231 -8
  64. data/lib/lutaml/xml/namespace.rb +18 -8
  65. data/lib/lutaml/xml/namespace_collector.rb +17 -1
  66. data/lib/lutaml/xml/namespace_needs.rb +77 -49
  67. data/lib/lutaml/xml/parse_session.rb +7 -19
  68. data/lib/lutaml/xml/schema/xsd/schema.rb +2 -1
  69. data/lib/lutaml/xml/serialization/format_conversion.rb +5 -1
  70. data/lib/lutaml/xml/transformation/ordered_applier.rb +21 -15
  71. data/lib/lutaml/xml/transformation.rb +15 -1
  72. data/lib/lutaml/xml/type/serializers.rb +8 -1
  73. data/lib/lutaml/yaml/adapter/yeptris_adapter.rb +34 -2
  74. data/lib/tasks/performance_probe_plan_walk.rb +1 -1
  75. data/lutaml-model.gemspec +7 -4
  76. data/spec/lutaml/json/yeptris_adapter_spec.rb +7 -2
  77. data/spec/lutaml/key_value/transformation/value_serializer_spec.rb +9 -5
  78. data/spec/lutaml/key_value/transformation_spec.rb +87 -21
  79. data/spec/lutaml/model/any_element_mapping_spec.rb +41 -0
  80. data/spec/lutaml/model/collection_validation_spec.rb +265 -0
  81. data/spec/lutaml/model/desc_spec.rb +50 -0
  82. data/spec/lutaml/model/late_attribute_declaration_spec.rb +35 -0
  83. data/spec/lutaml/model/model_import_reentry_spec.rb +46 -0
  84. data/spec/lutaml/model/opal_smoke_spec.rb +17 -0
  85. data/spec/lutaml/model/schema/generate_many_spec.rb +61 -0
  86. data/spec/lutaml/model/schema/generation_regressions_spec.rb +139 -0
  87. data/spec/lutaml/model/schema/json_schema_spec.rb +1 -13
  88. data/spec/lutaml/model/schema/yaml_schema_spec.rb +0 -11
  89. data/spec/lutaml/model/serialize/attribute_definition_block_form_spec.rb +244 -0
  90. data/spec/lutaml/model/store_spec.rb +6 -2
  91. data/spec/lutaml/model/unregistered_context_resolution_spec.rb +34 -0
  92. data/spec/lutaml/model/writer_assigned_attribute_spec.rb +62 -0
  93. data/spec/lutaml/model/yeptris_error_parity_spec.rb +95 -0
  94. data/spec/lutaml/xml/adapter/empty_document_diagnostics_spec.rb +50 -0
  95. data/spec/lutaml/xml/adapter/over_cap_parse_spec.rb +105 -0
  96. data/spec/lutaml/xml/adapter_loader_spec.rb +14 -0
  97. data/spec/lutaml/xml/adapter_metadata_default_spec.rb +16 -0
  98. data/spec/lutaml/xml/attribute_ambiguity_surface_spec.rb +5 -0
  99. data/spec/lutaml/xml/clear_parse_state_spec.rb +6 -1
  100. data/spec/lutaml/xml/compiled_rule_namespace_spec.rb +5 -0
  101. data/spec/lutaml/xml/cross_register_plan_spec.rb +75 -0
  102. data/spec/lutaml/xml/doubly_defined_namespace_spec.rb +16 -8
  103. data/spec/lutaml/xml/element_order_tracking_spec.rb +37 -0
  104. data/spec/lutaml/xml/leptris_string_encoding_spec.rb +65 -0
  105. data/spec/lutaml/xml/opal_xml_spec.rb +47 -0
  106. data/spec/lutaml/xml/parse_dom_retention_spec.rb +34 -0
  107. data/spec/lutaml/xml/plan_fast_path_spec.rb +122 -14
  108. data/spec/lutaml/xml/plan_namespaced_serialize_spec.rb +157 -0
  109. data/spec/lutaml/xml/plan_ns_less_document_spec.rb +86 -0
  110. data/spec/lutaml/xml/plan_partition_spec.rb +1 -1
  111. data/spec/lutaml/xml/plan_type_ns_attribute_spec.rb +131 -0
  112. data/spec/lutaml/xml/plan_uri_exact_dispatch_spec.rb +287 -0
  113. data/spec/lutaml/xml/plan_walk_spec.rb +6 -6
  114. data/spec/lutaml/xml/plan_wildcard_any_spec.rb +96 -0
  115. data/spec/lutaml/yaml/yeptris_adapter_spec.rb +29 -0
  116. data/spec/person_spec.rb +8 -0
  117. metadata +48 -17
  118. data/lib/lutaml/xml/plan_compiler.rb +0 -332
  119. data/lib/lutaml/xml/plan_hydrator.rb +0 -367
  120. data/lib/lutaml/xml/plan_order.rb +0 -53
  121. data/lib/lutaml/xml/plan_serializer.rb +0 -252
  122. data/lib/lutaml/xml/plan_walk.rb +0 -30
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Xml
5
+ module Leptris
6
+ # The native emission lane of the plan fast path: instead of
7
+ # minting the output DOM node-by-node through the Ruby
8
+ # create_child chain, build a plan RESULT from ops
9
+ # (leptris_plan_result_build), render it through the engine's
10
+ # plan serializer, reparse the compact output, and write it with
11
+ # the engine's indented document writer. Both renderings are
12
+ # pure C; measured 2.4x faster than the DOM-mint loop on the
13
+ # 500-row audit at a modest allocation increase.
14
+ #
15
+ # Byte-parity with the DOM lane holds because both lanes end at
16
+ # the same document writer over structurally identical
17
+ # documents: the compact emission escapes exactly as the DOM
18
+ # text nodes do, and reparsing yields one text node per leaf,
19
+ # matching the minted tree. The reparse is an engine-internal
20
+ # cost, not a Ruby one.
21
+ #
22
+ # Emission is enabled for the provably static subset: trees with
23
+ # no namespace form anywhere (every spelling is the unwritten
24
+ # local name, nothing to declare, no instance-dependent
25
+ # prefix/default flips) and the non-ordered build() shapes
26
+ # (attributes, scalars, collections, nested models). Wildcard,
27
+ # raw, callback, and content rows and ordered models keep the
28
+ # DOM lane — their emission shapes are not expressible as ops
29
+ # (or not verified to be) and the DOM lane is byte-exact.
30
+ module NativeEmission
31
+ ROOT_ROW = 0xFFFF_FFFF
32
+ EMITTABLE_KINDS = %i[scalar collection nested].freeze
33
+
34
+ class << self
35
+ def compatible?(plan)
36
+ !plan[:ordered] && tree_emittable?(plan[:tree])
37
+ end
38
+
39
+ # Pre-order depth-first, children in row order — the same
40
+ # walk Descriptor.flatten_plan performs, so the plan_index
41
+ # counters here match the engine's flattened plans array.
42
+ def tree_emittable?(tree)
43
+ return false if tree[:ns]
44
+ return false if (tree[:attributes] || []).any? { |a| a[:ns].is_a?(Hash) }
45
+
46
+ (tree[:children] || []).all? do |row|
47
+ EMITTABLE_KINDS.include?(row[:kind]) &&
48
+ (row[:kind] != :nested || !row[:plan] || tree_emittable?(row[:plan]))
49
+ end
50
+ end
51
+
52
+ def emit(instance, plan, register)
53
+ ops = [{ kind: :element, row_index: ROOT_ROW }]
54
+ emit_element(ops, instance, plan, register, 0)
55
+ ops << { kind: :end }
56
+ compact = plan[:descriptor].serialize(
57
+ plan[:descriptor].build_result(ops),
58
+ )
59
+ ::Leptris::XML.parse(compact).to_xml(indent: 2, no_decl: true)
60
+ end
61
+
62
+ private
63
+
64
+ # plan_index: this plan's flattened index. The engine's
65
+ # flatten_plan pushes each nested row's child plan exactly
66
+ # once — collection members share that slot — so child flat
67
+ # indices come from a static per-plan layout (relative to
68
+ # this plan's index), never from a runtime counter.
69
+ def emit_element(ops, instance, plan, register, plan_index)
70
+ my_index = plan_index
71
+ layout = flat_layout_for(plan)
72
+
73
+ emit_attributes(ops, instance, plan, my_index, register)
74
+
75
+ plan[:rows].each_with_index do |(rule, attr, kind, _spelling, delegate), row_index|
76
+ value = value_of(instance, attr, delegate)
77
+ next unless rule.render?(value, instance)
78
+
79
+ case kind
80
+ when :scalar
81
+ # Multi-capture parity: an attribute that collected
82
+ # several occurrences serializes one element per item,
83
+ # as the DOM lane does.
84
+ if value.is_a?(::Array)
85
+ value.each do |item|
86
+ ops << scalar_op(my_index, row_index, attr, item, register)
87
+ end
88
+ else
89
+ ops << scalar_op(my_index, row_index, attr, value, register)
90
+ end
91
+ when :collection_native, :collection_cb
92
+ Array(value).each do |item|
93
+ ops << scalar_op(my_index, row_index, attr, item, register)
94
+ end
95
+ when :nested
96
+ _tree, child_rel = layout[row_index]
97
+ next unless child_rel
98
+
99
+ Array(value).each do |item|
100
+ child_plan = nested_plans_for(plan)[[item.class, rule.name.to_s]] ||
101
+ (nested_plans_for(plan)[[item.class, rule.name.to_s]] =
102
+ PlanCompiler.compile(item.class, register))
103
+ ops << { kind: :element, plan_index: my_index,
104
+ row_index: row_index }
105
+ emit_element(ops, item, child_plan,
106
+ register, my_index + child_rel)
107
+ ops << { kind: :end }
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ # {nested row_index => [child tree, relative flat index]} —
114
+ # children of this plan occupy slots my_index+1 .. in row
115
+ # order, each nested row's subtree sized once.
116
+ def flat_layout_for(plan)
117
+ plan[:flat_layout] ||= begin
118
+ layout = {}
119
+ rel = 1
120
+ (plan[:tree][:children] || []).each_with_index do |row, i|
121
+ next unless row[:kind] == :nested && row[:plan]
122
+
123
+ layout[i] = [row[:plan], rel]
124
+ rel += subtree_size(row[:plan])
125
+ end
126
+ layout
127
+ end
128
+ end
129
+
130
+ def subtree_size(tree)
131
+ 1 + (tree[:children] || []).sum do |row|
132
+ row[:kind] == :nested && row[:plan] ? subtree_size(row[:plan]) : 0
133
+ end
134
+ end
135
+
136
+ def emit_attributes(ops, instance, plan, plan_index, register)
137
+ recorded = instance.attribute_order
138
+ attr_rows = plan[:attr_rows]
139
+ if recorded && !recorded.empty?
140
+ by_name = {}
141
+ attr_rows.each { |rule, attr, _sp, delegate| by_name[rule.name.to_s] = [rule, attr, delegate] }
142
+ names = recorded | attr_rows.map { |rule, _a, _s, _d| rule.name.to_s }
143
+ else
144
+ names = attr_rows.map { |rule, _a, _s, _d| rule.name.to_s }
145
+ by_name = {}
146
+ attr_rows.each { |rule, attr, _sp, delegate| by_name[rule.name.to_s] = [rule, attr, delegate] }
147
+ end
148
+ tree_attrs = plan[:tree][:attributes] || []
149
+ names.each do |name|
150
+ entry = by_name[name]
151
+ next unless entry
152
+
153
+ _rule, attr, delegate = entry
154
+ value = value_of(instance, attr, delegate)
155
+ next if value.nil?
156
+
157
+ row_index = tree_attrs.index { |a| a[:name] == name }
158
+ next unless row_index
159
+
160
+ ops << { kind: :attr, plan_index: plan_index,
161
+ row_index: row_index, name: name,
162
+ value: attr.serialize(value, :xml, register).to_s }
163
+ end
164
+ end
165
+
166
+ def scalar_op(plan_index, row_index, attr, value, register)
167
+ { kind: :scalar, plan_index: plan_index, row_index: row_index,
168
+ value: attr.serialize(value, :xml, register).to_s }
169
+ end
170
+
171
+ def value_of(instance, attr, delegate)
172
+ if delegate
173
+ target = instance.public_send(delegate)
174
+ target&.public_send(attr.name)
175
+ else
176
+ instance.public_send(attr.name)
177
+ end
178
+ end
179
+
180
+ def nested_plans_for(plan)
181
+ plan[:nested_plans] ||= {}
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end