lutaml-model 0.7.1 → 0.7.7

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 (259) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.github/workflows/dependent-tests.yml +4 -0
  4. data/.github/workflows/rake.yml +12 -0
  5. data/.github/workflows/release.yml +3 -0
  6. data/.gitignore +6 -1
  7. data/.irbrc +1 -0
  8. data/.pryrc +1 -0
  9. data/.rubocop.yml +1 -1
  10. data/.rubocop_todo.yml +58 -84
  11. data/Gemfile +4 -1
  12. data/README.adoc +3115 -365
  13. data/RELEASE_NOTES.adoc +346 -0
  14. data/docs/custom_adapters.adoc +144 -0
  15. data/docs/custom_registers.adoc +228 -0
  16. data/docs/schema_generation.adoc +898 -0
  17. data/docs/schema_import.adoc +364 -0
  18. data/flake.lock +114 -0
  19. data/flake.nix +103 -0
  20. data/lib/lutaml/model/attribute.rb +242 -100
  21. data/lib/lutaml/model/choice.rb +30 -0
  22. data/lib/lutaml/model/collection.rb +212 -0
  23. data/lib/lutaml/model/comparable_model.rb +3 -3
  24. data/lib/lutaml/model/config.rb +71 -42
  25. data/lib/lutaml/model/constants.rb +2 -0
  26. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  27. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  28. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  29. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  30. data/lib/lutaml/model/error/polymorphic_error.rb +7 -2
  31. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  32. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  33. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  34. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  35. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  36. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  37. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  38. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  39. data/lib/lutaml/model/error.rb +10 -0
  40. data/lib/lutaml/model/errors.rb +36 -0
  41. data/lib/lutaml/model/format_registry.rb +44 -0
  42. data/lib/lutaml/model/global_register.rb +41 -0
  43. data/lib/lutaml/model/hash_adapter/document.rb +11 -0
  44. data/lib/lutaml/model/hash_adapter/mapping.rb +19 -0
  45. data/lib/lutaml/model/hash_adapter/mapping_rule.rb +9 -0
  46. data/lib/lutaml/model/hash_adapter/standard_adapter.rb +17 -0
  47. data/lib/lutaml/model/hash_adapter/transform.rb +8 -0
  48. data/lib/lutaml/model/hash_adapter.rb +21 -0
  49. data/lib/lutaml/model/json/document.rb +11 -0
  50. data/lib/lutaml/model/json/mapping.rb +19 -0
  51. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  52. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  53. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  54. data/lib/lutaml/model/json/transform.rb +8 -0
  55. data/lib/lutaml/model/json.rb +21 -0
  56. data/lib/lutaml/model/jsonl/document.rb +14 -0
  57. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  58. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  59. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  60. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  61. data/lib/lutaml/model/jsonl.rb +21 -0
  62. data/lib/lutaml/model/key_value_document.rb +28 -0
  63. data/lib/lutaml/model/mapping/key_value_mapping.rb +85 -13
  64. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +5 -0
  65. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  66. data/lib/lutaml/model/mapping/mapping_rule.rb +15 -9
  67. data/lib/lutaml/model/register.rb +105 -0
  68. data/lib/lutaml/model/{toml_adapter.rb → registrable.rb} +1 -1
  69. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  70. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  71. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  72. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  73. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  74. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  75. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  76. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  77. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  78. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  79. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  80. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  81. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  82. data/lib/lutaml/model/schema/renderer.rb +36 -0
  83. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  84. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  85. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  86. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  87. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  88. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  89. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  90. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  91. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  92. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  93. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  94. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  95. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  96. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  97. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  98. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  99. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  100. data/lib/lutaml/model/schema.rb +1 -0
  101. data/lib/lutaml/model/sequence.rb +60 -30
  102. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  103. data/lib/lutaml/model/serialize.rb +296 -548
  104. data/lib/lutaml/model/services/base.rb +11 -0
  105. data/lib/lutaml/model/services/logger.rb +54 -0
  106. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  107. data/lib/lutaml/model/services/transformer.rb +48 -0
  108. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  109. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  110. data/lib/lutaml/model/services/type/validator.rb +43 -0
  111. data/lib/lutaml/model/services/validator.rb +145 -0
  112. data/lib/lutaml/model/services.rb +5 -0
  113. data/lib/lutaml/model/toml/document.rb +11 -0
  114. data/lib/lutaml/model/toml/mapping.rb +27 -0
  115. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  116. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  117. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  118. data/lib/lutaml/model/toml/transform.rb +8 -0
  119. data/lib/lutaml/model/toml.rb +30 -0
  120. data/lib/lutaml/model/transform/key_value_transform.rb +297 -0
  121. data/lib/lutaml/model/transform/xml_transform.rb +228 -0
  122. data/lib/lutaml/model/transform.rb +93 -0
  123. data/lib/lutaml/model/type/boolean.rb +1 -1
  124. data/lib/lutaml/model/type/date.rb +1 -1
  125. data/lib/lutaml/model/type/date_time.rb +1 -1
  126. data/lib/lutaml/model/type/decimal.rb +11 -9
  127. data/lib/lutaml/model/type/float.rb +2 -1
  128. data/lib/lutaml/model/type/integer.rb +24 -21
  129. data/lib/lutaml/model/type/string.rb +4 -2
  130. data/lib/lutaml/model/type/time.rb +1 -1
  131. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  132. data/lib/lutaml/model/type/value.rb +11 -10
  133. data/lib/lutaml/model/type.rb +5 -2
  134. data/lib/lutaml/model/uninitialized_class.rb +1 -1
  135. data/lib/lutaml/model/utils.rb +56 -4
  136. data/lib/lutaml/model/validation.rb +6 -4
  137. data/lib/lutaml/model/version.rb +1 -1
  138. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  139. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  140. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  141. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +42 -25
  142. data/lib/lutaml/model/xml/element.rb +32 -0
  143. data/lib/lutaml/model/xml/mapping.rb +471 -0
  144. data/lib/lutaml/model/xml/mapping_rule.rb +149 -0
  145. data/lib/lutaml/model/xml/nokogiri_adapter.rb +234 -0
  146. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  147. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +7 -2
  148. data/lib/lutaml/model/xml/oga_adapter.rb +172 -0
  149. data/lib/lutaml/model/xml/ox_adapter.rb +229 -0
  150. data/lib/lutaml/model/xml/transform.rb +8 -0
  151. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  152. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +9 -31
  153. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  154. data/lib/lutaml/model/xml.rb +31 -0
  155. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  156. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  157. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  158. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  159. data/lib/lutaml/model/yaml/document.rb +10 -0
  160. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  161. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  162. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  163. data/lib/lutaml/model/yaml/transform.rb +8 -0
  164. data/lib/lutaml/model/yaml.rb +21 -0
  165. data/lib/lutaml/model/yamls/document.rb +14 -0
  166. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  167. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  168. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  169. data/lib/lutaml/model/yamls/transform.rb +19 -0
  170. data/lib/lutaml/model/yamls.rb +21 -0
  171. data/lib/lutaml/model.rb +15 -4
  172. data/lutaml-model.gemspec +0 -4
  173. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +5 -6
  174. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  175. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  176. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  177. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  178. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  179. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  180. data/spec/lutaml/model/attribute_spec.rb +41 -44
  181. data/spec/lutaml/model/cdata_spec.rb +7 -7
  182. data/spec/lutaml/model/choice_spec.rb +44 -0
  183. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  184. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  185. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  186. data/spec/lutaml/model/global_register_spec.rb +108 -0
  187. data/spec/lutaml/model/group_spec.rb +27 -10
  188. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  189. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  190. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  191. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  192. data/spec/lutaml/model/key_value_mapping_spec.rb +25 -1
  193. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  194. data/spec/lutaml/model/multiple_mapping_spec.rb +6 -6
  195. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  196. data/spec/lutaml/model/polymorphic_spec.rb +178 -0
  197. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  198. data/spec/lutaml/model/register/xml_spec.rb +185 -0
  199. data/spec/lutaml/model/register_spec.rb +147 -0
  200. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  201. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  202. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  203. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  204. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  205. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  206. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  207. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  208. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  209. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  210. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  211. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  212. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  213. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  214. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  215. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  216. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  217. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  218. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  219. data/spec/lutaml/model/sequence_spec.rb +36 -0
  220. data/spec/lutaml/model/serializable_spec.rb +201 -94
  221. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  222. data/spec/lutaml/model/toml_spec.rb +51 -0
  223. data/spec/lutaml/model/transformation_spec.rb +72 -15
  224. data/spec/lutaml/model/type_spec.rb +8 -4
  225. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  226. data/spec/lutaml/model/utils_spec.rb +3 -3
  227. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  228. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  229. data/spec/lutaml/model/xml/root_mappings/nested_child_mappings_spec.rb +217 -0
  230. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  231. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  232. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  233. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  234. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +12 -12
  235. data/spec/lutaml/model/xml_adapter_spec.rb +30 -6
  236. data/spec/lutaml/model/xml_mapping_rule_spec.rb +14 -7
  237. data/spec/lutaml/model/xml_mapping_spec.rb +27 -15
  238. data/spec/lutaml/model/xml_spec.rb +63 -0
  239. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  240. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  241. data/spec/lutaml/model/yamls_spec.rb +294 -0
  242. data/spec/spec_helper.rb +4 -3
  243. metadata +162 -60
  244. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  245. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  246. data/lib/lutaml/model/loggable.rb +0 -15
  247. data/lib/lutaml/model/mapping/json_mapping.rb +0 -17
  248. data/lib/lutaml/model/mapping/toml_mapping.rb +0 -25
  249. data/lib/lutaml/model/mapping/xml_mapping.rb +0 -389
  250. data/lib/lutaml/model/mapping/xml_mapping_rule.rb +0 -139
  251. data/lib/lutaml/model/mapping/yaml_mapping.rb +0 -17
  252. data/lib/lutaml/model/mapping.rb +0 -14
  253. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  254. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  255. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  256. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  257. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  258. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  259. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -47,12 +47,63 @@ module PolymorphicSpec
47
47
  end
48
48
  end
49
49
 
50
+ module InvalidClasses
51
+ class AnchorReference < Lutaml::Model::Serializable
52
+ attribute :anchor_id, :string
53
+
54
+ xml do
55
+ map_element "anchor_id", to: :anchor_id
56
+ end
57
+
58
+ key_value do
59
+ map "anchor_id", to: :anchor_id
60
+ end
61
+ end
62
+
63
+ class DocumentReference < Reference
64
+ attribute :document_id, :string
65
+
66
+ xml do
67
+ map_element "document_id", to: :document_id
68
+ end
69
+
70
+ key_value do
71
+ map "document_id", to: :document_id
72
+ end
73
+ end
74
+ end
75
+
50
76
  class ReferenceSet < Lutaml::Model::Serializable
51
77
  attribute :references, Reference, collection: true, polymorphic: [
52
78
  DocumentReference,
53
79
  AnchorReference,
54
80
  ]
55
81
  end
82
+
83
+ class SimpleReferenceSet < Lutaml::Model::Serializable
84
+ attribute :references, Reference, collection: true, polymorphic: true
85
+
86
+ xml do
87
+ root "ReferenceSet"
88
+ map_element "references", to: :references, polymorphic: {
89
+ attribute: "reference-type",
90
+ class_map: {
91
+ "document-ref" => "PolymorphicSpec::Base::DocumentReference",
92
+ "anchor-ref" => "PolymorphicSpec::Base::AnchorReference",
93
+ },
94
+ }
95
+ end
96
+
97
+ key_value do
98
+ map "references", to: :references, polymorphic: {
99
+ attribute: "_class",
100
+ class_map: {
101
+ "Document" => "PolymorphicSpec::Base::DocumentReference",
102
+ "Anchor" => "PolymorphicSpec::Base::AnchorReference",
103
+ },
104
+ }
105
+ end
106
+ end
56
107
  end
57
108
 
58
109
  module Child
@@ -226,6 +277,133 @@ RSpec.describe "Polymorphic" do
226
277
  end
227
278
  end
228
279
 
280
+ context "when using polymorphic: true option" do
281
+ context "when key_value formats" do
282
+ let(:reference_set) do
283
+ PolymorphicSpec::Base::SimpleReferenceSet.new(
284
+ references: [
285
+ PolymorphicSpec::Base::DocumentReference.new(
286
+ _class: "Document",
287
+ document_id: "book:tbtd",
288
+ name: "The Tibetan Book of the Dead",
289
+ ),
290
+ PolymorphicSpec::Base::AnchorReference.new(
291
+ _class: "Anchor",
292
+ anchor_id: "book:tbtd:anchor-1",
293
+ name: "Chapter 1",
294
+ ),
295
+ ],
296
+ )
297
+ end
298
+
299
+ let(:invalid_reference_set) do
300
+ PolymorphicSpec::Base::SimpleReferenceSet.new(
301
+ references: [
302
+ PolymorphicSpec::Base::InvalidClasses::DocumentReference.new(
303
+ _class: "Document",
304
+ document_id: "book:tbtd",
305
+ name: "The Tibetan Book of the Dead",
306
+ ),
307
+ PolymorphicSpec::Base::InvalidClasses::AnchorReference.new(
308
+ _class: "Anchor",
309
+ anchor_id: "book:tbtd:anchor-1",
310
+ name: "Chapter 1",
311
+ ),
312
+ ],
313
+ )
314
+ end
315
+
316
+ let(:yaml) do
317
+ <<~YAML
318
+ ---
319
+ references:
320
+ - _class: Document
321
+ name: The Tibetan Book of the Dead
322
+ document_id: book:tbtd
323
+ - _class: Anchor
324
+ name: Chapter 1
325
+ anchor_id: book:tbtd:anchor-1
326
+ YAML
327
+ end
328
+
329
+ let(:parsed_yaml) do
330
+ PolymorphicSpec::Base::SimpleReferenceSet.from_yaml(yaml)
331
+ end
332
+
333
+ let(:error_message) do
334
+ "PolymorphicSpec::Base::InvalidClasses::AnchorReference is not " \
335
+ "valid sub class of PolymorphicSpec::Base::Reference"
336
+ end
337
+
338
+ it "deserializes correctly" do
339
+ expect(parsed_yaml).to eq(reference_set)
340
+ end
341
+
342
+ it "serializes correctly" do
343
+ expect(parsed_yaml.to_yaml).to eq(yaml)
344
+ end
345
+
346
+ it "raises error" do
347
+ expect do
348
+ invalid_reference_set.validate!
349
+ end.to raise_error(Lutaml::Model::ValidationError, error_message)
350
+ end
351
+ end
352
+
353
+ context "when XML format" do
354
+ let(:reference_set) do
355
+ PolymorphicSpec::Base::SimpleReferenceSet.new(
356
+ references: [
357
+ PolymorphicSpec::Base::DocumentReference.new(
358
+ _class: "document-ref",
359
+ document_id: "book:tbtd",
360
+ name: "The Tibetan Book of the Dead",
361
+ ),
362
+ PolymorphicSpec::Base::AnchorReference.new(
363
+ _class: "anchor-ref",
364
+ anchor_id: "book:tbtd:anchor-1",
365
+ name: "Chapter 1",
366
+ ),
367
+ ],
368
+ )
369
+ end
370
+ let(:xml) do
371
+ <<~XML
372
+ <ReferenceSet>
373
+ <references reference-type="document-ref">
374
+ <name>The Tibetan Book of the Dead</name>
375
+ <document_id>book:tbtd</document_id>
376
+ </references>
377
+ <references reference-type="anchor-ref">
378
+ <name>Chapter 1</name>
379
+ <anchor_id>book:tbtd:anchor-1</anchor_id>
380
+ </references>
381
+ </ReferenceSet>
382
+ XML
383
+ end
384
+
385
+ let(:parsed_xml) do
386
+ PolymorphicSpec::Base::SimpleReferenceSet.from_xml(xml)
387
+ end
388
+
389
+ it "deserializes correctly" do
390
+ expect(parsed_xml).to eq(reference_set)
391
+ end
392
+
393
+ it "serializes correctly" do
394
+ expect(parsed_xml.to_xml.strip).to be_equivalent_to(xml.strip)
395
+ end
396
+
397
+ it "does not raise error if polymorphic is set to true" do
398
+ expect { reference_set.validate! }.not_to raise_error
399
+ end
400
+
401
+ it "has empty errors array on validate" do
402
+ expect(reference_set.validate).to eq([])
403
+ end
404
+ end
405
+ end
406
+
229
407
  context "when defined in child class" do
230
408
  context "when key_value formats" do
231
409
  let(:reference_set) do
@@ -0,0 +1,275 @@
1
+ require "spec_helper"
2
+
3
+ module RegisterKeyValueSpec
4
+ class GeoCoordinate < Lutaml::Model::Serializable
5
+ attribute :latitude, :float
6
+ attribute :longitude, :float
7
+
8
+ json do
9
+ map :lat, to: :latitude
10
+ map :lng, to: :longitude
11
+ end
12
+ end
13
+
14
+ class Address < Lutaml::Model::Serializable
15
+ attribute :street, :string
16
+ attribute :city, :string
17
+ attribute :location, GeoCoordinate
18
+
19
+ json do
20
+ map :street, to: :street
21
+ map :city, to: :city
22
+ map :geo, to: :location
23
+ end
24
+ end
25
+
26
+ class ContactInfo < Lutaml::Model::Serializable
27
+ attribute :phone, :string
28
+ attribute :email, :string
29
+
30
+ json do
31
+ map :phoneNumber, to: :phone
32
+ map :email, to: :email
33
+ end
34
+ end
35
+
36
+ class Person < Lutaml::Model::Serializable
37
+ attribute :name, :string
38
+ attribute :age, :integer
39
+ attribute :address, :address
40
+ attribute :contact, :contact_info
41
+ attribute :tags, :string, collection: true
42
+
43
+ json do
44
+ map :name, to: :name
45
+ map :age, to: :age
46
+ map :address, to: :address
47
+ map :contactInfo, to: :contact
48
+ map :tags, to: :tags
49
+ end
50
+ end
51
+
52
+ class EnhancedContactInfo < Lutaml::Model::Serializable
53
+ attribute :phone, :string
54
+ attribute :email, :string
55
+ attribute :preferred, :boolean
56
+
57
+ json do
58
+ map :phoneNumber, to: :phone
59
+ map :email, to: :email
60
+ map :isPrimary, to: :preferred
61
+ end
62
+ end
63
+ end
64
+
65
+ RSpec.describe "RegisterKeyValueSpec" do
66
+ let(:register) { Lutaml::Model::Register.new(:json_test_register) }
67
+ let(:person) { RegisterKeyValueSpec::Person.from_json(json, register: register) }
68
+
69
+ before do
70
+ # Register the registers in the global registry
71
+ Lutaml::Model::GlobalRegister.register(register)
72
+
73
+ # Register all the V1 model classes in the register
74
+ register.register_model_tree(RegisterKeyValueSpec::Person)
75
+ register.register_model(RegisterKeyValueSpec::Address)
76
+ register.register_model(RegisterKeyValueSpec::GeoCoordinate)
77
+ register.register_model(RegisterKeyValueSpec::ContactInfo)
78
+ end
79
+
80
+ describe "parsing JSON" do
81
+ let(:json) do
82
+ <<~JSON
83
+ {
84
+ "name": "John Doe",
85
+ "age": 30,
86
+ "address": {
87
+ "street": "123 Main St",
88
+ "city": "Anytown",
89
+ "geo": {
90
+ "lat": 40.7128,
91
+ "lng": -74.0060
92
+ }
93
+ },
94
+ "contactInfo": {
95
+ "phoneNumber": "555-1234",
96
+ "email": "john@example.com"
97
+ },
98
+ "tags": ["developer", "musician"]
99
+ }
100
+ JSON
101
+ end
102
+
103
+ it "parses JSON into model objects" do
104
+ expect(person).to be_a(RegisterKeyValueSpec::Person)
105
+ expect(person.name).to eq("John Doe")
106
+ expect(person.age).to eq(30)
107
+ expect(person.address).to be_a(RegisterKeyValueSpec::Address)
108
+ expect(person.address.street).to eq("123 Main St")
109
+ expect(person.address.city).to eq("Anytown")
110
+ expect(person.address.location).to be_a(RegisterKeyValueSpec::GeoCoordinate)
111
+ expect(person.address.location.latitude).to eq(40.7128)
112
+ expect(person.address.location.longitude).to eq(-74.0060)
113
+ expect(person.contact).to be_a(RegisterKeyValueSpec::ContactInfo)
114
+ expect(person.contact.phone).to eq("555-1234")
115
+ expect(person.contact.email).to eq("john@example.com")
116
+ expect(person.tags).to eq(["developer", "musician"])
117
+ end
118
+
119
+ it "serializes model objects back to JSON" do
120
+ json_output = person.to_json
121
+ parsed_json = JSON.parse(json_output)
122
+
123
+ expect(parsed_json["name"]).to eq("John Doe")
124
+ expect(parsed_json["age"]).to eq(30)
125
+ expect(parsed_json["address"]["street"]).to eq("123 Main St")
126
+ expect(parsed_json["address"]["city"]).to eq("Anytown")
127
+ expect(parsed_json["address"]["geo"]["lat"]).to eq(40.7128)
128
+ expect(parsed_json["address"]["geo"]["lng"]).to eq(-74.0060)
129
+ expect(parsed_json["contactInfo"]["phoneNumber"]).to eq("555-1234")
130
+ expect(parsed_json["contactInfo"]["email"]).to eq("john@example.com")
131
+ expect(parsed_json["tags"]).to eq(["developer", "musician"])
132
+ end
133
+ end
134
+
135
+ describe "using global type substitution with JSON" do
136
+ let(:register_substitution) do
137
+ register.register_global_type_substitution(
138
+ from_type: RegisterKeyValueSpec::ContactInfo,
139
+ to_type: RegisterKeyValueSpec::EnhancedContactInfo,
140
+ )
141
+ end
142
+
143
+ let(:json) do
144
+ <<~JSON
145
+ {
146
+ "name": "Jane Smith",
147
+ "age": 28,
148
+ "address": {
149
+ "street": "456 Oak Ave",
150
+ "city": "Somewhere",
151
+ "geo": {
152
+ "lat": 37.7749,
153
+ "lng": -122.4194
154
+ }
155
+ },
156
+ "contactInfo": {
157
+ "phoneNumber": "555-5678",
158
+ "email": "jane@example.com",
159
+ "isPrimary": true
160
+ },
161
+ "tags": ["designer"]
162
+ }
163
+ JSON
164
+ end
165
+
166
+ context "when the substitute class is not registered" do
167
+ it "deserializes contactInfo using ContactInfo class" do
168
+ expect(person.contact).to be_a(RegisterKeyValueSpec::ContactInfo)
169
+ expect(person.contact).not_to respond_to(:preferred)
170
+ expect(person.contact.phone).to eq("555-5678")
171
+ expect(person.contact.email).to eq("jane@example.com")
172
+ end
173
+ end
174
+
175
+ context "when the substitute class is registered" do
176
+ it "deserializes contactInfo using EnhancedContactInfo class" do
177
+ register.register_model(RegisterKeyValueSpec::EnhancedContactInfo)
178
+ register_substitution
179
+
180
+ enhanced_person = RegisterKeyValueSpec::Person.from_json(json, register: register.id)
181
+
182
+ expect(enhanced_person.contact).to be_a(RegisterKeyValueSpec::EnhancedContactInfo)
183
+ expect(enhanced_person.contact).to respond_to(:preferred)
184
+ expect(enhanced_person.contact.phone).to eq("555-5678")
185
+ expect(enhanced_person.contact.email).to eq("jane@example.com")
186
+ expect(enhanced_person.contact.preferred).to be(true)
187
+
188
+ # Ensure serialization includes the new field
189
+ json_output = enhanced_person.to_json
190
+ parsed_json = JSON.parse(json_output)
191
+ expect(parsed_json["contactInfo"]["isPrimary"]).to be(true)
192
+ end
193
+ end
194
+ end
195
+
196
+ describe "handling complex nested JSON arrays" do
197
+ let(:complex_json) do
198
+ <<~JSON
199
+ {
200
+ "name": "Team Alpha",
201
+ "members": [
202
+ {
203
+ "name": "Alice",
204
+ "age": 32,
205
+ "address": {
206
+ "street": "789 Pine St",
207
+ "city": "Elsewhere"
208
+ }
209
+ },
210
+ {
211
+ "name": "Bob",
212
+ "age": 29,
213
+ "address": {
214
+ "street": "321 Elm St",
215
+ "city": "Nowhere"
216
+ }
217
+ }
218
+ ]
219
+ }
220
+ JSON
221
+ end
222
+
223
+ before do
224
+ stub_const("RegisterKeyValueSpec::Team", Class.new(Lutaml::Model::Serializable) do
225
+ attribute :name, :string
226
+ attribute :members, :person, collection: true
227
+
228
+ json do
229
+ map :name, to: :name
230
+ map :members, to: :members
231
+ end
232
+ end)
233
+
234
+ register.register_model(RegisterKeyValueSpec::Team)
235
+ end
236
+
237
+ it "correctly handles arrays of complex objects" do
238
+ team = RegisterKeyValueSpec::Team.from_json(complex_json, register: register)
239
+
240
+ expect(team.name).to eq("Team Alpha")
241
+ expect(team.members.size).to eq(2)
242
+ expect(team.members[0].name).to eq("Alice")
243
+ expect(team.members[0].age).to eq(32)
244
+ expect(team.members[0].address.street).to eq("789 Pine St")
245
+ expect(team.members[1].name).to eq("Bob")
246
+ expect(team.members[1].age).to eq(29)
247
+ expect(team.members[1].address.street).to eq("321 Elm St")
248
+
249
+ # Test round-trip serialization
250
+ toml_output = team.to_toml
251
+ team2 = RegisterKeyValueSpec::Team.from_toml(toml_output, register: register)
252
+
253
+ expect(team2).to eq(team)
254
+ expect(team2.members[0].name).to eq("Alice")
255
+ expect(team2.members[1].name).to eq("Bob")
256
+ end
257
+ end
258
+
259
+ describe "#resolve" do
260
+ before do
261
+ stub_const("RegisterKeyValueSpec::Team", Class.new(Lutaml::Model::Serializable))
262
+ register.register_model(RegisterKeyValueSpec::Team)
263
+ end
264
+
265
+ it "resolves a class" do
266
+ expect(register.resolve("RegisterKeyValueSpec::Team")).to eq(RegisterKeyValueSpec::Team)
267
+ expect(register.resolve(:"RegisterKeyValueSpec::Team")).to eq(RegisterKeyValueSpec::Team)
268
+ expect(register.resolve(RegisterKeyValueSpec::Team)).to eq(RegisterKeyValueSpec::Team)
269
+ end
270
+
271
+ it "returns nil for an unknown class" do
272
+ expect(register.resolve("UnknownClass")).to be_nil
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,185 @@
1
+ require "spec_helper"
2
+
3
+ module RegisterXmlSpec
4
+ class String < Lutaml::Model::Type::String
5
+ def to_xml
6
+ "custom_string: #{value}"
7
+ end
8
+ end
9
+
10
+ class Mi < Lutaml::Model::Serializable
11
+ attribute :value, :string
12
+
13
+ xml do
14
+ root "mi"
15
+ namespace "http://www.w3.org/1998/Math/MathML"
16
+
17
+ map_content to: :value
18
+ end
19
+ end
20
+
21
+ # Define a MathML Operator element class
22
+ class Mo < Lutaml::Model::Serializable
23
+ attribute :value, :string
24
+
25
+ xml do
26
+ root "mo"
27
+ namespace "http://www.w3.org/1998/Math/MathML"
28
+
29
+ map_content to: :value
30
+ end
31
+ end
32
+
33
+ # Define a MathML Fraction element
34
+ class Mfrac < Lutaml::Model::Serializable
35
+ attribute :numerator, :mo
36
+ attribute :denominator, :mi
37
+
38
+ xml do
39
+ root "mfrac"
40
+ namespace "http://www.w3.org/1998/Math/MathML"
41
+
42
+ map_element "mo", to: :numerator
43
+ map_element "mi", to: :denominator
44
+ end
45
+ end
46
+
47
+ # Define a full MathML expression
48
+ class Math < Lutaml::Model::Serializable
49
+ attribute :symbol, :mi
50
+ attribute :operator, :mo
51
+ attribute :fraction, :mfrac
52
+
53
+ xml do
54
+ root "math"
55
+ namespace "http://www.w3.org/1998/Math/MathML"
56
+
57
+ map_element "mi", to: :symbol
58
+ map_element "mo", to: :operator
59
+ map_element "mfrac", to: :fraction
60
+ end
61
+ end
62
+
63
+ class NewMi < Lutaml::Model::Serializable
64
+ attribute :value, :string
65
+ attribute :color, :string
66
+
67
+ xml do
68
+ root "mi"
69
+ namespace "http://www.w3.org/1998/Math/MathML"
70
+
71
+ map_content to: :value
72
+ map_attribute :color, to: :color
73
+ end
74
+ end
75
+ end
76
+
77
+ RSpec.describe "RegisterXmlSpec" do
78
+ let(:register) { Lutaml::Model::Register.new(:mathml_register) }
79
+ let(:formula) { RegisterXmlSpec::Math.from_xml(xml, register: register) }
80
+
81
+ before do
82
+ # Register the register in the global registry
83
+ Lutaml::Model::GlobalRegister.register(register)
84
+
85
+ # Register all the model classes
86
+ register.register_model_tree(RegisterXmlSpec::Math)
87
+ register.register_model(RegisterXmlSpec::Mi)
88
+ register.register_model(RegisterXmlSpec::Mo)
89
+ register.register_model(RegisterXmlSpec::Mfrac)
90
+ end
91
+
92
+ describe "parsing MathML XML" do
93
+ let(:xml) do
94
+ <<~XML
95
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
96
+ <mi>x</mi>
97
+ <mo>=</mo>
98
+ <mfrac>
99
+ <mo>a</mo>
100
+ <mi>b</mi>
101
+ </mfrac>
102
+ </math>
103
+ XML
104
+ end
105
+
106
+ let(:instantiated) do
107
+ register.get_class(:math).new(
108
+ symbol: RegisterXmlSpec::Mi.new(value: "x"),
109
+ operator: RegisterXmlSpec::Mo.new(value: "="),
110
+ fraction: RegisterXmlSpec::Mfrac.new(
111
+ {
112
+ numerator: RegisterXmlSpec::Mo.new(value: "a"),
113
+ denominator: RegisterXmlSpec::Mi.new(value: "b"),
114
+ __register: register,
115
+ },
116
+ ),
117
+ )
118
+ end
119
+
120
+ it "parses MathML XML into model objects" do
121
+ expect(formula).to be_a(RegisterXmlSpec::Math)
122
+ expect(formula.symbol.value).to eq("x")
123
+ expect(formula.operator.value).to eq("=")
124
+ expect(formula.fraction.numerator.value).to eq("a")
125
+ expect(formula.fraction.denominator.value).to eq("b")
126
+ end
127
+
128
+ it "serializes model objects back to MathML XML" do
129
+ expect(formula.to_xml).to be_equivalent_to(xml)
130
+ end
131
+
132
+ it "instantiates the model correctly" do
133
+ expect(instantiated.to_xml).to be_equivalent_to(xml)
134
+ end
135
+ end
136
+
137
+ describe "using global type substitution with MathML" do
138
+ let(:register_substitution) do
139
+ register.register_global_type_substitution(
140
+ from_type: RegisterXmlSpec::Mi,
141
+ to_type: RegisterXmlSpec::NewMi,
142
+ )
143
+ register.register_global_type_substitution(
144
+ from_type: Lutaml::Model::Type::String,
145
+ to_type: RegisterXmlSpec::String,
146
+ )
147
+ end
148
+
149
+ let(:xml) do
150
+ <<~XML
151
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
152
+ <mi color="red">y</mi>
153
+ </math>
154
+ XML
155
+ end
156
+
157
+ let(:xml_type_substituted) do
158
+ <<~XML
159
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
160
+ <mi color="custom_string: red">y</mi>
161
+ </math>
162
+ XML
163
+ end
164
+
165
+ context "when substitute class is not registered" do
166
+ it "serializes mi tag using Mi class" do
167
+ expect(formula.symbol).to be_a(RegisterXmlSpec::Mi)
168
+ expect(formula.symbol).not_to respond_to(:color)
169
+ expect(formula.symbol.value).to eq("y")
170
+ expect(formula.to_xml).not_to be_equivalent_to(xml)
171
+ end
172
+ end
173
+
174
+ context "when substitute class is registered" do
175
+ it "serializes mi tag using NewMi class" do
176
+ register_substitution
177
+ expect(formula.symbol).to be_a(RegisterXmlSpec::NewMi)
178
+ expect(formula.symbol).to respond_to(:color)
179
+ expect(formula.symbol.color).to eq("red")
180
+ expect(formula.symbol.value).to eq("y")
181
+ expect(formula.to_xml).to be_equivalent_to(xml_type_substituted)
182
+ end
183
+ end
184
+ end
185
+ end