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
@@ -0,0 +1,55 @@
1
+ <?xml version="1.0"?>
2
+ <ProductCatalog version="1.0" lastUpdated="2024-03-20T10:00:00+00:00">
3
+ <categories>
4
+ <category>
5
+ <id>1</id>
6
+ <name>Electronics</name>
7
+ <parentCategory>0</parentCategory>
8
+ <description>Electronic devices and accessories</description>
9
+ </category>
10
+ <category>
11
+ <id>2</id>
12
+ <name>Smartphones</name>
13
+ <parentCategory>1</parentCategory>
14
+ <description>Mobile phones and accessories</description>
15
+ </category>
16
+ <category>
17
+ <id>3</id>
18
+ <name>Laptops</name>
19
+ <parentCategory>1</parentCategory>
20
+ <description>Portable computers and accessories</description>
21
+ </category>
22
+ </categories>
23
+ <products>
24
+ <product id="1" status="active">
25
+ <sku>PHN12345678</sku>
26
+ <name>Smartphone Pro</name>
27
+ <description>Latest smartphone model</description>
28
+ <category>
29
+ <id>2</id>
30
+ <name>Smartphones</name>
31
+ <parentCategory>1</parentCategory>
32
+ <description>Mobile phones and accessories</description>
33
+ </category>
34
+ <price>99.99</price>
35
+ <salePrice>89.99</salePrice>
36
+ <inventory>50</inventory>
37
+ <dimensions>
38
+ <length>14.5</length>
39
+ <width>7.0</width>
40
+ <height>0.7</height>
41
+ <weight>0.18</weight>
42
+ </dimensions>
43
+ <rating>4.5</rating>
44
+ <features>
45
+ <feature>5G Connectivity</feature>
46
+ <feature>128GB Storage</feature>
47
+ <feature>Triple Camera System</feature>
48
+ </features>
49
+ <images>
50
+ <image url="https://example.com/images/smartphone-pro-1.jpg" alt="Smartphone Pro Front View" isPrimary="true"/>
51
+ <image url="https://example.com/images/smartphone-pro-2.jpg" alt="Smartphone Pro Back View"/>
52
+ </images>
53
+ </product>
54
+ </products>
55
+ </ProductCatalog>
@@ -0,0 +1,43 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ProductCatalog version="1.0" lastUpdated="2024-03-20T10:00:00+00:00">
3
+ <categories>
4
+ <category>
5
+ <id>1</id>
6
+ <name>Electronics</name>
7
+ <parentCategory>0</parentCategory>
8
+ <description>Electronic devices and accessories</description>
9
+ </category>
10
+ </categories>
11
+ <products>
12
+ <product id="1" status="active">
13
+ <sku>ABC12345678</sku>
14
+ <name>Smartphone X</name>
15
+ <description>A high-end smartphone with advanced features</description>
16
+ <category>
17
+ <id>1</id>
18
+ <name>Electronics</name>
19
+ <parentCategory>0</parentCategory>
20
+ <description>Electronic devices and accessories</description>
21
+ </category>
22
+ <price>99.99</price>
23
+ <salePrice>89.99</salePrice>
24
+ <inventory>100</inventory>
25
+ <dimensions>
26
+ <length>15.0</length>
27
+ <width>7.5</width>
28
+ <height>0.8</height>
29
+ <weight>0.2</weight>
30
+ </dimensions>
31
+ <rating>4.5</rating>
32
+ <features>
33
+ <feature>5G Connectivity</feature>
34
+ <feature>128GB Storage</feature>
35
+ <feature>Triple Camera System</feature>
36
+ </features>
37
+ <images>
38
+ <image url="https://example.com/images/smartphone-x-1.jpg" alt="Smartphone X Front View" isPrimary="true"/>
39
+ <image url="https://example.com/images/smartphone-x-2.jpg" alt="Smartphone X Back View"/>
40
+ </images>
41
+ </product>
42
+ </products>
43
+ </ProductCatalog>
@@ -0,0 +1,151 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
3
+ xmlns:cat="http://www.example.com/catalog" targetNamespace="http://www.example.com/catalog" elementFormDefault="qualified">
4
+
5
+ <!-- Attributes -->
6
+ <xs:attribute name="id" type="xs:integer" use="required"/>
7
+
8
+ <!-- Simple Types -->
9
+ <xs:simpleType name="SKUType">
10
+ <xs:annotation>
11
+ <xs:documentation>Stock Keeping Unit - must be 8-12 alphanumeric characters</xs:documentation>
12
+ </xs:annotation>
13
+ <xs:restriction base="xs:string">
14
+ <xs:pattern value="[A-Z0-9]{8,12}"/>
15
+ </xs:restriction>
16
+ </xs:simpleType>
17
+
18
+ <xs:simpleType name="PriceType">
19
+ <xs:annotation>
20
+ <xs:documentation>Price with 2 decimal places, must be positive</xs:documentation>
21
+ </xs:annotation>
22
+ <xs:restriction base="xs:decimal">
23
+ <xs:maxExclusive value="101.00"/>
24
+ <xs:minInclusive value="0.01"/>
25
+ </xs:restriction>
26
+ </xs:simpleType>
27
+
28
+ <xs:simpleType name="QuantityTypes">
29
+ <xs:annotation>
30
+ <xs:documentation>Quantity must be non-negative</xs:documentation>
31
+ </xs:annotation>
32
+ <xs:restriction base="xs:integer">
33
+ <xs:minInclusive value="0"/>
34
+ </xs:restriction>
35
+ </xs:simpleType>
36
+
37
+ <xs:simpleType name="RatingType">
38
+ <xs:annotation>
39
+ <xs:documentation>Product rating from 1 to 5</xs:documentation>
40
+ </xs:annotation>
41
+ <xs:restriction base="xs:decimal">
42
+ <xs:minInclusive value="1"/>
43
+ <xs:maxInclusive value="5"/>
44
+ </xs:restriction>
45
+ </xs:simpleType>
46
+
47
+ <!-- Complex Types -->
48
+ <xs:complexType name="DimensionsType">
49
+ <xs:sequence>
50
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
51
+ <xs:choice maxOccurs="unbounded">
52
+ <xs:element name="length" type="xs:decimal">
53
+ <xs:annotation>
54
+ <xs:documentation>Length in centimeters</xs:documentation>
55
+ </xs:annotation>
56
+ </xs:element>
57
+ </xs:choice>
58
+ <xs:element name="width" type="xs:decimal">
59
+ <xs:annotation>
60
+ <xs:documentation>Width in centimeters</xs:documentation>
61
+ </xs:annotation>
62
+ </xs:element>
63
+ </xs:choice>
64
+ <xs:element name="height" type="xs:decimal">
65
+ <xs:annotation>
66
+ <xs:documentation>Height in centimeters</xs:documentation>
67
+ </xs:annotation>
68
+ </xs:element>
69
+ <xs:element name="weight" type="xs:decimal">
70
+ <xs:annotation>
71
+ <xs:documentation>Weight in kilograms</xs:documentation>
72
+ </xs:annotation>
73
+ </xs:element>
74
+ </xs:sequence>
75
+ </xs:complexType>
76
+
77
+ <xs:complexType name="CategoryType">
78
+ <xs:sequence>
79
+ <xs:element name="id" type="xs:integer"/>
80
+ <xs:element name="name" type="xs:string"/>
81
+ <xs:element name="parentCategory" type="xs:integer" minOccurs="0"/>
82
+ <xs:element name="description" type="xs:string" minOccurs="0"/>
83
+ </xs:sequence>
84
+ </xs:complexType>
85
+
86
+ <xs:complexType name="ProductType">
87
+ <xs:sequence>
88
+ <xs:element name="sku" type="cat:SKUType"/>
89
+ <xs:element name="name" type="xs:string"/>
90
+ <xs:element name="description" type="xs:string"/>
91
+ <xs:element name="category" type="cat:CategoryType"/>
92
+ <xs:element name="price" type="cat:PriceType"/>
93
+ <xs:element name="salePrice" type="cat:PriceType" minOccurs="0"/>
94
+ <xs:element name="inventory" type="cat:QuantityTypes"/>
95
+ <xs:element name="dimensions" type="cat:DimensionsType"/>
96
+ <xs:element name="rating" type="cat:RatingType" minOccurs="0"/>
97
+ <xs:element name="features" minOccurs="0">
98
+ <xs:complexType>
99
+ <xs:sequence>
100
+ <xs:element name="feature" type="xs:string" maxOccurs="unbounded"/>
101
+ </xs:sequence>
102
+ </xs:complexType>
103
+ </xs:element>
104
+ <xs:element name="images" minOccurs="0">
105
+ <xs:complexType>
106
+ <xs:sequence>
107
+ <xs:element name="image" maxOccurs="unbounded">
108
+ <xs:complexType>
109
+ <xs:attribute name="url" type="xs:anyURI" use="required"/>
110
+ <xs:attribute name="alt" type="xs:string" use="required"/>
111
+ <xs:attribute name="isPrimary" type="xs:boolean" default="false"/>
112
+ </xs:complexType>
113
+ </xs:element>
114
+ </xs:sequence>
115
+ </xs:complexType>
116
+ </xs:element>
117
+ </xs:sequence>
118
+ <!-- using 'ref' for development purposes only -->
119
+ <xs:attribute ref="id"/>
120
+ <xs:attribute name="status" use="required">
121
+ <xs:simpleType>
122
+ <xs:restriction base="xs:string">
123
+ <xs:enumeration value="active"/>
124
+ <xs:enumeration value="inactive"/>
125
+ <xs:enumeration value="discontinued"/>
126
+ </xs:restriction>
127
+ </xs:simpleType>
128
+ </xs:attribute>
129
+ </xs:complexType>
130
+
131
+ <xs:complexType name="ProductCatalog" mixed="true">
132
+ <xs:sequence>
133
+ <xs:element name="categories">
134
+ <xs:complexType>
135
+ <xs:sequence>
136
+ <xs:element name="category" type="cat:CategoryType" maxOccurs="unbounded"/>
137
+ </xs:sequence>
138
+ </xs:complexType>
139
+ </xs:element>
140
+ <xs:element name="products">
141
+ <xs:complexType>
142
+ <xs:sequence>
143
+ <xs:element name="product" type="cat:ProductType" maxOccurs="unbounded"/>
144
+ </xs:sequence>
145
+ </xs:complexType>
146
+ </xs:element>
147
+ </xs:sequence>
148
+ <xs:attribute name="version" type="xs:string" use="required"/>
149
+ <xs:attribute name="lastUpdated" type="xs:dateTime" use="required"/>
150
+ </xs:complexType>
151
+ </xs:schema>
@@ -0,0 +1,38 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://example.com/specs"
3
+ xmlns="http://example.com/specs" elementFormDefault="qualified">
4
+ <!-- ComplexType SpecBase and ShortSpec are root elements -->
5
+
6
+ <!-- Base type for a specification -->
7
+ <xs:complexType name="spec">
8
+ <xs:sequence>
9
+ <xs:element name="title" type="xs:string"/>
10
+ <xs:element name="version" type="xs:string"/>
11
+ <xs:element name="status" type="status" minOccurs="0" default="draft"/>
12
+ </xs:sequence>
13
+ <xs:attribute name="id" type="xs:ID" use="required"/>
14
+ </xs:complexType>
15
+
16
+ <!-- Restriction: A "ShortSpec" only allows title and version, not status -->
17
+ <xs:complexType name="shortSpec">
18
+ <xs:complexContent>
19
+ <xs:restriction base="spec">
20
+ <xs:sequence>
21
+ <xs:element name="title" type="xs:string"/>
22
+ <xs:element name="version" type="xs:string"/>
23
+ <xs:element name="status" type="status" minOccurs="0" maxOccurs="2"/>
24
+ </xs:sequence>
25
+ <xs:attribute name="id" type="xs:ID" use="required"/>
26
+ </xs:restriction>
27
+ </xs:complexContent>
28
+ </xs:complexType>
29
+
30
+ <!-- Enumeration for status -->
31
+ <xs:simpleType name="status">
32
+ <xs:restriction base="xs:string">
33
+ <xs:enumeration value="draft"/>
34
+ <xs:enumeration value="final"/>
35
+ <xs:enumeration value="archived"/>
36
+ </xs:restriction>
37
+ </xs:simpleType>
38
+ </xs:schema>
@@ -0,0 +1,101 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module AttributeCollection
5
+ class StringParts < Lutaml::Model::Collection
6
+ instances :parts, :string
7
+
8
+ def to_s
9
+ parts.join(" -- ")
10
+ end
11
+ end
12
+
13
+ class BibliographicItem < Lutaml::Model::Serializable
14
+ attribute :title_parts, :string, collection: StringParts
15
+
16
+ xml do
17
+ root "titles"
18
+ map_element "title", to: :title_parts
19
+ end
20
+
21
+ key_value do
22
+ root "titles"
23
+ map_instances to: :title_parts
24
+ end
25
+
26
+ def render_title
27
+ title_parts.to_s
28
+ end
29
+ end
30
+ end
31
+
32
+ RSpec.describe AttributeCollection do
33
+ let(:xml_data) do
34
+ <<~XML
35
+ <titles>
36
+ <title>Title One</title>
37
+ <title>Title Two</title>
38
+ <title>Title Three</title>
39
+ </titles>
40
+ XML
41
+ end
42
+
43
+ let(:yaml_data) do
44
+ <<~YAML
45
+ ---
46
+ titles:
47
+ - Title One
48
+ - Title Two
49
+ - Title Three
50
+ YAML
51
+ end
52
+
53
+ describe "BibliographicItem with custom collection" do
54
+ let(:bib_item) { AttributeCollection::BibliographicItem.from_xml(xml_data) }
55
+
56
+ it "initializes with custom collection class" do
57
+ expect(bib_item.title_parts).to be_a(AttributeCollection::StringParts)
58
+ end
59
+
60
+ it "contains the correct number of title parts" do
61
+ expect(bib_item.title_parts.count).to eq(3)
62
+ end
63
+
64
+ it "contains the correct title parts" do
65
+ expect(bib_item.title_parts.parts).to eq(["Title One", "Title Two", "Title Three"])
66
+ end
67
+
68
+ it "renders title with custom separator" do
69
+ expect(bib_item.render_title).to eq("Title One -- Title Two -- Title Three")
70
+ end
71
+
72
+ it "serializes to XML correctly" do
73
+ expect(bib_item.to_xml.strip).to eq(xml_data.strip)
74
+ end
75
+
76
+ it "deserializes from YAML correctly" do
77
+ yaml_bib_item = AttributeCollection::BibliographicItem.from_yaml(yaml_data)
78
+ expect(yaml_bib_item.title_parts.parts).to eq(["Title One", "Title Two", "Title Three"])
79
+ end
80
+
81
+ it "serializes to YAML correctly" do
82
+ expect(bib_item.to_yaml.strip).to eq(yaml_data.strip)
83
+ end
84
+
85
+ it "can be initialized with an array of strings" do
86
+ bib_item = AttributeCollection::BibliographicItem.new(
87
+ title_parts: AttributeCollection::StringParts.new(
88
+ ["Part One", "Part Two"],
89
+ ),
90
+ )
91
+
92
+ expect(bib_item.title_parts.parts).to eq(["Part One", "Part Two"])
93
+ end
94
+
95
+ it "can be initialized with a custom collection instance" do
96
+ collection = AttributeCollection::StringParts.new(["Custom One", "Custom Two"])
97
+ bib_item = AttributeCollection::BibliographicItem.new(title_parts: collection)
98
+ expect(bib_item.title_parts.parts).to eq(["Custom One", "Custom Two"])
99
+ end
100
+ end
101
+ end
@@ -15,6 +15,7 @@ RSpec.describe Lutaml::Model::Attribute do
15
15
  Class.new(Lutaml::Model::Serializable) do
16
16
  attribute :age, :integer
17
17
  attribute :image, :string
18
+ restrict :image, collection: 1..., pattern: /.*\.\w+$/
18
19
  end
19
20
  end
20
21
 
@@ -44,6 +45,45 @@ RSpec.describe Lutaml::Model::Attribute do
44
45
  )
45
46
  end
46
47
 
48
+ describe "#validate_name!" do
49
+ Lutaml::Model::Serializable.instance_methods.each do |method|
50
+ if Lutaml::Model::Attribute::ALLOW_OVERRIDING.include?(method)
51
+ before do
52
+ allow(Lutaml::Model::Logger).to receive(:warn)
53
+ end
54
+
55
+ it "does not raise an error when method is `#{method}`" do
56
+ expect do
57
+ Class.new(Lutaml::Model::Serializable) do
58
+ attribute method, :string
59
+ end
60
+ end.not_to raise_error
61
+ end
62
+
63
+ it "logs a warning, when method is `#{method}`" do
64
+ Class.new(Lutaml::Model::Serializable) do
65
+ attribute method, :string
66
+ end
67
+
68
+ expect(Lutaml::Model::Logger)
69
+ .to have_received(:warn)
70
+ .with("Attribute name `#{method}` conflicts with a built-in method")
71
+ end
72
+ else
73
+ it "raise exception, when method is `#{method}`" do
74
+ expect do
75
+ Class.new(Lutaml::Model::Serializable) do
76
+ attribute method, :string
77
+ end
78
+ end.to raise_error(
79
+ Lutaml::Model::InvalidAttributeNameError,
80
+ "Attribute name '#{method}' is not allowed",
81
+ )
82
+ end
83
+ end
84
+ end
85
+ end
86
+
47
87
  describe "#validate_options!" do
48
88
  let(:validate_options) { name_attr.method(:validate_options!) }
49
89
 
@@ -56,7 +96,7 @@ RSpec.describe Lutaml::Model::Attribute do
56
96
  it "raise exception if option is not allowed" do
57
97
  expect do
58
98
  validate_options.call({ foo: "bar" })
59
- end.to raise_error(StandardError, "Invalid options given for `name` [:foo]")
99
+ end.to raise_error(Lutaml::Model::InvalidAttributeOptionsError, "Invalid options given for `name` [:foo]")
60
100
  end
61
101
 
62
102
  it "raise exception if pattern is given with non string type" do
@@ -71,49 +111,6 @@ RSpec.describe Lutaml::Model::Attribute do
71
111
  end
72
112
  end
73
113
 
74
- describe "#validate_type!" do
75
- let(:validate_type) { name_attr.method(:validate_type!) }
76
-
77
- it "return true if type is a class" do
78
- expect(validate_type.call(TestRecord)).to be(true)
79
- end
80
-
81
- it "return true if type is a valid symbol" do
82
- expect(validate_type.call(:string)).to be(true)
83
- end
84
-
85
- it "return true if type is a valid string" do
86
- expect(validate_type.call("String")).to be(true)
87
- end
88
-
89
- it "raise exception if type is invalid symbol" do
90
- expect do
91
- validate_type.call(:foo)
92
- end.to raise_error(StandardError, "Unknown Lutaml::Model::Type: foo")
93
- end
94
-
95
- it "raise exception if type is invalid string" do
96
- expect do
97
- validate_type.call("foo")
98
- end.to raise_error(StandardError, "Unknown Lutaml::Model::Type: foo")
99
- end
100
- end
101
-
102
- describe "#cast_type!" do
103
- it "cast :string to Lutaml::Model::Type::String" do
104
- expect(name_attr.cast_type!(:string)).to eq(Lutaml::Model::Type::String)
105
- end
106
-
107
- it "cast :integer to Lutaml::Model::Type::Integer" do
108
- expect(name_attr.cast_type!(:integer)).to eq(Lutaml::Model::Type::Integer)
109
- end
110
-
111
- it "raises ArgumentError on unknown type" do
112
- expect { name_attr.cast_type!(:foobar) }
113
- .to raise_error(ArgumentError, "Unknown Lutaml::Model::Type: foobar")
114
- end
115
- end
116
-
117
114
  describe "#derived?" do
118
115
  context "when type is set" do
119
116
  let(:attribute) { described_class.new("name", :string) }
@@ -1,7 +1,7 @@
1
1
  require "spec_helper"
2
2
  require "lutaml/model"
3
- require "lutaml/model/xml_adapter/nokogiri_adapter"
4
- require "lutaml/model/xml_adapter/ox_adapter"
3
+ require "lutaml/model/xml/nokogiri_adapter"
4
+ require "lutaml/model/xml/ox_adapter"
5
5
 
6
6
  module CDATA
7
7
  class Beta < Lutaml::Model::Serializable
@@ -350,7 +350,7 @@ RSpec.describe "CDATA" do
350
350
  instance = parent_mapper.from_xml(input_xml)
351
351
  result_xml = parent_mapper.to_xml(instance)
352
352
 
353
- expected_output = adapter_class == Lutaml::Model::XmlAdapter::OxAdapter ? expected_ox_xml : expected_nokogiri_xml
353
+ expected_output = adapter_class == Lutaml::Model::Xml::OxAdapter ? expected_ox_xml : expected_nokogiri_xml
354
354
 
355
355
  expect(result_xml.strip).to eq(expected_output.strip)
356
356
  end
@@ -439,7 +439,7 @@ RSpec.describe "CDATA" do
439
439
 
440
440
  # due to the difference in capturing
441
441
  # newlines in ox and nokogiri adapters
442
- if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
442
+ if adapter_class == Lutaml::Model::Xml::OxAdapter
443
443
  expected_xml = expected_ox_xml
444
444
  end
445
445
 
@@ -496,7 +496,7 @@ RSpec.describe "CDATA" do
496
496
 
497
497
  # due to the difference in capturing
498
498
  # newlines in ox and nokogiri adapters
499
- if adapter_class == Lutaml::Model::XmlAdapter::OxAdapter
499
+ if adapter_class == Lutaml::Model::Xml::OxAdapter
500
500
  expected_xml = expected_ox_xml
501
501
  end
502
502
 
@@ -509,11 +509,11 @@ RSpec.describe "CDATA" do
509
509
  end
510
510
  end
511
511
 
512
- describe Lutaml::Model::XmlAdapter::NokogiriAdapter do
512
+ describe Lutaml::Model::Xml::NokogiriAdapter do
513
513
  it_behaves_like "cdata behavior", described_class
514
514
  end
515
515
 
516
- describe Lutaml::Model::XmlAdapter::OxAdapter do
516
+ describe Lutaml::Model::Xml::OxAdapter do
517
517
  it_behaves_like "cdata behavior", described_class
518
518
  end
519
519
  end
@@ -59,6 +59,29 @@ module ChoiceSpec
59
59
  map :last_name, to: :last_name
60
60
  end
61
61
  end
62
+
63
+ class ContactEmail < Lutaml::Model::Serializable
64
+ attribute :email, :string
65
+ xml { no_root }
66
+ end
67
+
68
+ class ContactPhone < Lutaml::Model::Serializable
69
+ attribute :phone, :string
70
+ xml { no_root }
71
+ end
72
+
73
+ class Person < Lutaml::Model::Serializable
74
+ choice(min: 1, max: 2) do
75
+ import_model_attributes ContactEmail
76
+ import_model_attributes ContactPhone
77
+ end
78
+
79
+ xml do
80
+ root "Person"
81
+ map_element :email, to: :email
82
+ map_element :phone, to: :phone
83
+ end
84
+ end
62
85
  end
63
86
 
64
87
  RSpec.describe "Choice" do
@@ -165,4 +188,25 @@ RSpec.describe "Choice" do
165
188
  end.to raise_error(Lutaml::Model::InvalidChoiceRangeError, "Choice lower bound `-1` must be positive")
166
189
  end
167
190
  end
191
+
192
+ context "with choice using import_model_attributes" do
193
+ it "parses XML with both email and phone using choice and import_model_attributes" do
194
+ xml = <<~XML
195
+ <Person>
196
+ <email>john.doe@example.com</email>
197
+ <phone>1234567890</phone>
198
+ </Person>
199
+ XML
200
+ person = ChoiceSpec::Person.from_xml(xml)
201
+ expect(person.email).to eq("john.doe@example.com")
202
+ expect(person.phone).to eq("1234567890")
203
+ end
204
+
205
+ it "raises error if no contact info is provided" do
206
+ xml = <<~XML
207
+ <Person></Person>
208
+ XML
209
+ expect { ChoiceSpec::Person.from_xml(xml).validate! }.to raise_error(Lutaml::Model::ValidationError)
210
+ end
211
+ end
168
212
  end