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
@@ -1,7 +1,7 @@
1
1
  module Lutaml
2
2
  module Model
3
3
  class Attribute
4
- attr_reader :name, :type, :options
4
+ attr_reader :name, :options
5
5
 
6
6
  ALLOWED_OPTIONS = %i[
7
7
  raw
@@ -17,23 +17,88 @@ module Lutaml
17
17
  polymorphic
18
18
  polymorphic_class
19
19
  initialize_empty
20
+ validations
20
21
  ].freeze
21
22
 
23
+ MODEL_STRINGS = [
24
+ Lutaml::Model::Type::String,
25
+ "String",
26
+ :string,
27
+ ].freeze
28
+
29
+ # Safe methods than can be overriden without any crashing
30
+ ALLOW_OVERRIDING = %i[
31
+ display
32
+ validate
33
+ hash
34
+ itself
35
+ taint
36
+ untaint
37
+ trust
38
+ untrust
39
+ methods
40
+ instance_variables
41
+ tap
42
+ extend
43
+ freeze
44
+ encoding
45
+ method
46
+ object_id
47
+ ].freeze
48
+
49
+ def self.cast_type!(type)
50
+ case type
51
+ when Symbol then cast_from_symbol!(type)
52
+ when String then cast_from_string!(type)
53
+ when Class then type
54
+ else
55
+ raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
56
+ end
57
+ end
58
+
59
+ def self.cast_from_symbol!(type)
60
+ Type.lookup(type)
61
+ rescue UnknownTypeError
62
+ raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
63
+ end
64
+
65
+ def self.cast_from_string!(type)
66
+ Type.const_get(type)
67
+ rescue NameError
68
+ raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
69
+ end
70
+
22
71
  def initialize(name, type, options = {})
72
+ validate_name!(
73
+ name, reserved_methods: Lutaml::Model::Serializable.instance_methods
74
+ )
75
+
23
76
  @name = name
24
77
  @options = options
25
78
 
26
79
  validate_presence!(type, options[:method_name])
27
- process_type!(type) if type
80
+ @type = type
28
81
  process_options!
29
82
  end
30
83
 
84
+ def type(register_id = nil)
85
+ return if unresolved_type.nil?
86
+
87
+ register_id ||= Lutaml::Model::Config.default_register
88
+ register = Lutaml::Model::GlobalRegister.lookup(register_id)
89
+ register.get_class_without_register(unresolved_type)
90
+ end
91
+
92
+ def unresolved_type
93
+ @type
94
+ end
95
+
31
96
  def polymorphic?
32
97
  @options[:polymorphic_class]
33
98
  end
34
99
 
35
100
  def derived?
36
- type.nil?
101
+ unresolved_type.nil?
37
102
  end
38
103
 
39
104
  def delegate
@@ -52,45 +117,61 @@ module Lutaml
52
117
  @options[:initialize_empty]
53
118
  end
54
119
 
120
+ def validations
121
+ @options[:validations]
122
+ end
123
+
55
124
  def cast_type!(type)
56
- case type
57
- when Symbol
58
- begin
59
- Type.lookup(type)
60
- rescue UnknownTypeError
61
- raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
62
- end
63
- when String
64
- begin
65
- Type.const_get(type)
66
- rescue NameError
67
- raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
68
- end
69
- when Class
70
- type
71
- else
72
- raise ArgumentError, "Unknown Lutaml::Model::Type: #{type}"
73
- end
125
+ self.class.cast_type!(type)
74
126
  end
75
127
 
76
- def cast_value(value)
77
- return type.cast(value) unless value.is_a?(Array)
128
+ def cast_value(value, register)
129
+ return cast_element(value, register) unless collection_instance?(value)
130
+
131
+ build_collection(value.map { |v| cast_element(v, register) })
132
+ end
133
+
134
+ def cast_element(value, register)
135
+ resolved_type = type(register)
136
+ return resolved_type.new(value) if value.is_a?(Hash) && !hash_type?
137
+
138
+ resolved_type.cast(value)
139
+ end
78
140
 
79
- value.map { |v| type.cast(v) }
141
+ def hash_type?
142
+ type == Lutaml::Model::Type::Hash
80
143
  end
81
144
 
82
145
  def setter
83
146
  :"#{@name}="
84
147
  end
85
148
 
149
+ def collection
150
+ @options[:collection]
151
+ end
152
+
86
153
  def collection?
87
- options[:collection] || false
154
+ collection || false
88
155
  end
89
156
 
90
157
  def singular?
91
158
  !collection?
92
159
  end
93
160
 
161
+ def collection_class
162
+ return Array unless custom_collection?
163
+
164
+ collection
165
+ end
166
+
167
+ def collection_instance?(value)
168
+ value.is_a?(collection_class)
169
+ end
170
+
171
+ def build_collection(*args)
172
+ collection_class.new(args.flatten)
173
+ end
174
+
94
175
  def raw?
95
176
  @raw
96
177
  end
@@ -99,13 +180,13 @@ module Lutaml
99
180
  !enum_values.empty?
100
181
  end
101
182
 
102
- def default
103
- cast_value(default_value)
183
+ def default(register = Lutaml::Model::Config.default_register)
184
+ cast_value(default_value(register), register)
104
185
  end
105
186
 
106
- def default_value
187
+ def default_value(register)
107
188
  if delegate
108
- type.attributes[to].default
189
+ type(register).attributes[to].default(register)
109
190
  elsif options[:default].is_a?(Proc)
110
191
  options[:default].call
111
192
  elsif options.key?(:default)
@@ -115,8 +196,8 @@ module Lutaml
115
196
  end
116
197
  end
117
198
 
118
- def default_set?
119
- !Utils.uninitialized?(default_value)
199
+ def default_set?(register)
200
+ !Utils.uninitialized?(default_value(register))
120
201
  end
121
202
 
122
203
  def pattern
@@ -153,8 +234,8 @@ module Lutaml
153
234
  options[:values].include?(value)
154
235
  end
155
236
 
156
- def valid_pattern!(value)
157
- return true unless type == Lutaml::Model::Type::String
237
+ def valid_pattern!(value, resolved_type)
238
+ return true unless resolved_type == Lutaml::Model::Type::String
158
239
  return true unless pattern
159
240
 
160
241
  unless pattern.match?(value)
@@ -174,32 +255,48 @@ module Lutaml
174
255
  # 2. Value count should be between the collection range if defined
175
256
  # e.g if collection: 0..5 is set then the value greater then 5
176
257
  # will raise `Lutaml::Model::CollectionCountOutOfRangeError`
177
- def validate_value!(value)
258
+ def validate_value!(value, register)
178
259
  # Use the default value if the value is nil
179
- value = default if value.nil?
260
+ value = default(register) if value.nil?
261
+ resolved_type = type(register)
180
262
 
181
263
  valid_value!(value) &&
182
264
  valid_collection!(value, self) &&
183
- valid_pattern!(value) &&
184
- validate_polymorphic!(value)
265
+ valid_pattern!(value, resolved_type) &&
266
+ validate_polymorphic!(value, resolved_type) &&
267
+ execute_validations!(value)
185
268
  end
186
269
 
187
- def validate_polymorphic(value)
188
- return value.all? { |v| validate_polymorphic!(v) } if value.is_a?(Array)
189
- return true unless polymorphic_enabled?
270
+ # execute custom validations on the attribute value
271
+ # i.e presence: true, numericality: true, etc
272
+ def execute_validations!(value)
273
+ return true if Utils.blank?(value)
274
+
275
+ memoization_container = {}
276
+ errors = Lutaml::Model::Validator.call(value, validations, memoization_container)
190
277
 
191
- valid_polymorphic_type?(value)
278
+ return if errors.empty?
279
+
280
+ raise Lutaml::Model::ValidationFailedError.new(errors)
281
+ end
282
+
283
+ def validate_polymorphic(value, resolved_type)
284
+ return value.all? { |v| validate_polymorphic!(v, resolved_type) } if value.is_a?(Array)
285
+ return true unless options[:polymorphic]
286
+
287
+ valid_polymorphic_type?(value, resolved_type)
192
288
  end
193
289
 
194
- def validate_polymorphic!(value)
195
- return true if validate_polymorphic(value)
290
+ def validate_polymorphic!(value, resolved_type)
291
+ return true if validate_polymorphic(value, resolved_type)
196
292
 
197
- raise Lutaml::Model::PolymorphicError.new(value, options)
293
+ raise Lutaml::Model::PolymorphicError.new(value, options, resolved_type)
198
294
  end
199
295
 
200
296
  def validate_collection_range
201
297
  range = @options[:collection]
202
298
  return if range == true
299
+ return if custom_collection?
203
300
 
204
301
  unless range.is_a?(Range)
205
302
  raise ArgumentError, "Invalid collection range: #{range}"
@@ -228,14 +325,14 @@ module Lutaml
228
325
  end
229
326
 
230
327
  def valid_collection!(value, caller)
231
- raise Lutaml::Model::CollectionTrueMissingError.new(name, caller) if value.is_a?(Array) && !collection?
328
+ raise Lutaml::Model::CollectionTrueMissingError.new(name, caller) if collection_instance?(value) && !collection?
232
329
 
233
330
  return true unless collection?
234
331
 
235
332
  # Allow any value for unbounded collections
236
333
  return true if options[:collection] == true
237
334
 
238
- unless value.is_a?(Array)
335
+ unless collection_instance?(value)
239
336
  raise Lutaml::Model::CollectionCountOutOfRangeError.new(
240
337
  name,
241
338
  value,
@@ -246,7 +343,7 @@ module Lutaml
246
343
  range = options[:collection]
247
344
  return true unless range.is_a?(Range)
248
345
 
249
- if range.end.nil?
346
+ if range.is_a?(Range) && range.end.nil?
250
347
  if value.size < range.begin
251
348
  raise Lutaml::Model::CollectionCountOutOfRangeError.new(
252
349
  name,
@@ -254,7 +351,7 @@ module Lutaml
254
351
  range,
255
352
  )
256
353
  end
257
- elsif !range.cover?(value.size)
354
+ elsif range.is_a?(Range) && !range.cover?(value.size)
258
355
  raise Lutaml::Model::CollectionCountOutOfRangeError.new(
259
356
  name,
260
357
  value,
@@ -263,39 +360,86 @@ module Lutaml
263
360
  end
264
361
  end
265
362
 
266
- def serialize(value, format, options = {})
267
- value ||= [] if collection? && initialize_empty?
363
+ def serialize(value, format, register, options = {})
364
+ value ||= build_collection if collection? && initialize_empty?
268
365
  return value if value.nil? || Utils.uninitialized?(value)
269
366
  return value if derived?
270
- return serialize_array(value, format, options) if value.is_a?(Array)
271
- return serialize_model(value, format, options) if type <= Serialize
272
367
 
273
- serialize_value(value, format)
274
- end
368
+ resolved_type = options[:resolved_type] || type(register)
369
+ serialize_options = options.merge(resolved_type: resolved_type)
370
+ return serialize_array(value, format, register, serialize_options) if collection_instance?(value)
371
+ return serialize_model(value, format, register, options) if resolved_type <= Serialize
275
372
 
276
- def cast(value, format, options = {})
277
- value ||= [] if collection? && !value.nil?
278
- return value.map { |v| cast(v, format, options) } if value.is_a?(Array)
373
+ serialize_value(value, format, resolved_type)
374
+ end
279
375
 
280
- return value if already_serialized?(type, value)
376
+ def cast(value, format, register, options = {})
377
+ resolved_type = options[:resolved_type] || type(register)
378
+ return build_collection(value.map { |v| cast(v, format, register, options.merge(resolved_type: resolved_type)) }) if collection_instance?(value) || value.is_a?(Array)
281
379
 
282
- klass = resolve_polymorphic_class(type, value, options)
380
+ return value if already_serialized?(resolved_type, value)
283
381
 
382
+ klass = resolve_polymorphic_class(resolved_type, value, options)
284
383
  if can_serialize?(klass, value, format)
285
- klass.apply_mappings(value, format, options)
384
+ klass.apply_mappings(value, format, options.merge(register: register))
286
385
  elsif needs_conversion?(klass, value)
287
386
  klass.send(:"from_#{format}", value)
288
387
  else
289
- klass.cast(value)
388
+ # No need to use register#get_class,
389
+ # can_serialize? method already checks if type is Serializable or not.
390
+ Type.lookup(klass).cast(value)
290
391
  end
291
392
  end
292
393
 
394
+ def serializable?(register)
395
+ type(register) <= Serialize
396
+ end
397
+
398
+ def collection_range
399
+ return unless collection?
400
+
401
+ collection.is_a?(Range) ? collection : 0..Float::INFINITY
402
+ end
403
+
404
+ def sequenced_appearance_count(element_order, mapped_name, current_index)
405
+ elements = element_order[current_index..]
406
+ element_count = elements.take_while { |element| element == mapped_name }.count
407
+ return element_count if element_count.between?(*collection_range.minmax)
408
+
409
+ raise Lutaml::Model::ElementCountOutOfRangeError.new(
410
+ mapped_name,
411
+ element_count,
412
+ collection_range,
413
+ )
414
+ end
415
+
416
+ def process_options!
417
+ validate_options!(@options)
418
+ @raw = !!@options[:raw]
419
+ @validations = @options[:validations]
420
+ set_default_for_collection if collection?
421
+ end
422
+
293
423
  def deep_dup
294
- self.class.new(name, type, Utils.deep_dup(options))
424
+ self.class.new(name, unresolved_type, Utils.deep_dup(options))
295
425
  end
296
426
 
297
427
  private
298
428
 
429
+ def validate_name!(name, reserved_methods:)
430
+ return unless reserved_methods.include?(name.to_sym)
431
+
432
+ if ALLOW_OVERRIDING.include?(name.to_sym)
433
+ warn_name_conflict(name)
434
+ else
435
+ raise Lutaml::Model::InvalidAttributeNameError.new(name)
436
+ end
437
+ end
438
+
439
+ def warn_name_conflict(name)
440
+ Logger.warn("Attribute name `#{name}` conflicts with a built-in method")
441
+ end
442
+
299
443
  def resolve_polymorphic_class(type, value, options)
300
444
  return type unless polymorphic_map_defined?(options, value)
301
445
 
@@ -304,47 +448,54 @@ module Lutaml
304
448
  Object.const_get(klass_name)
305
449
  end
306
450
 
307
- def polymorphic_map_defined?(options, value)
451
+ def polymorphic_map_defined?(polymorphic_options, value)
308
452
  !value.nil? &&
309
- options[:polymorphic] &&
310
- !options[:polymorphic].empty? &&
311
- value[options[:polymorphic][:attribute]]
453
+ polymorphic_options[:polymorphic] &&
454
+ !polymorphic_options[:polymorphic].empty? &&
455
+ value[polymorphic_options[:polymorphic][:attribute]]
312
456
  end
313
457
 
314
458
  def castable?(value, format)
315
459
  value.is_a?(Hash) ||
316
- (format == :xml && value.is_a?(Lutaml::Model::XmlAdapter::XmlElement))
317
- end
318
-
319
- def castable_serialized_type?(value)
320
- type <= Serialize && value.is_a?(type.model)
460
+ (format == :xml && value.is_a?(Lutaml::Model::Xml::XmlElement))
321
461
  end
322
462
 
323
463
  def can_serialize?(klass, value, format)
324
464
  klass <= Serialize && castable?(value, format)
325
465
  end
326
466
 
467
+ def custom_collection?
468
+ return false if singular?
469
+ return false if collection == true
470
+ return false if collection.is_a?(Range)
471
+
472
+ collection <= Lutaml::Model::Collection
473
+ end
474
+
327
475
  def needs_conversion?(klass, value)
328
- !value.nil? && !value.is_a?(klass)
476
+ !value.nil? && !value.is_a?(klass) && Utils.initialized?(value)
329
477
  end
330
478
 
331
479
  def already_serialized?(klass, value)
332
480
  klass <= Serialize && value.is_a?(klass.model)
333
481
  end
334
482
 
335
- def serialize_array(value, format, options)
336
- value.map { |v| serialize(v, format, options) }
483
+ def serialize_array(value, format, register, options)
484
+ value.map { |v| serialize(v, format, register, options) }
337
485
  end
338
486
 
339
- def serialize_model(value, format, options)
487
+ def serialize_model(value, format, register, options)
488
+ as_options = options.merge(register: register)
340
489
  return unless Utils.present?(value)
341
- return value.class.as(format, value, options) if value.is_a?(type)
342
490
 
343
- type.as(format, value, options)
491
+ resolved_type = as_options.delete(:resolved_type) || type(register)
492
+ return value.class.as(format, value, as_options) if value.is_a?(resolved_type)
493
+
494
+ resolved_type.as(format, value, as_options)
344
495
  end
345
496
 
346
- def serialize_value(value, format)
347
- value = type.new(value) unless value.is_a?(Type::Value)
497
+ def serialize_value(value, format, resolved_type)
498
+ value = resolved_type.new(value) unless value.is_a?(Type::Value)
348
499
  value.send(:"to_#{format}")
349
500
  end
350
501
 
@@ -354,29 +505,19 @@ module Lutaml
354
505
  raise ArgumentError, "method or type must be set for an attribute"
355
506
  end
356
507
 
357
- def process_type!(type)
358
- validate_type!(type)
359
- @type = cast_type!(type)
360
- end
361
-
362
- def process_options!
363
- validate_options!(@options)
364
- @raw = !!@options[:raw]
365
- set_default_for_collection if collection?
366
- end
367
-
368
508
  def set_default_for_collection
369
509
  validate_collection_range
370
- @options[:default] ||= -> { [] } if initialize_empty?
510
+ @options[:default] ||= -> { build_collection } if initialize_empty?
371
511
  end
372
512
 
373
513
  def validate_options!(options)
374
514
  if (invalid_opts = options.keys - ALLOWED_OPTIONS).any?
375
- raise StandardError,
376
- "Invalid options given for `#{name}` #{invalid_opts}"
515
+ raise Lutaml::Model::InvalidAttributeOptionsError.new(name, invalid_opts)
377
516
  end
378
517
 
379
- if options.key?(:pattern) && type != Lutaml::Model::Type::String
518
+ # No need to change user register#get_class, only checks if type is LutaML-Model string.
519
+ # Using MODEL_STRINGS since pattern is only supported for String type.
520
+ if options.key?(:pattern) && !MODEL_STRINGS.include?(type)
380
521
  raise StandardError,
381
522
  "Invalid option `pattern` given for `#{name}`, " \
382
523
  "`pattern` is only allowed for :string type"
@@ -390,19 +531,20 @@ module Lutaml
390
531
  end
391
532
 
392
533
  def validate_type!(type)
393
- return true if type.is_a?(Class)
394
- return true if [Symbol, String].include?(type.class) && cast_type!(type)
534
+ return true if [Symbol, String].include?(type.class) || type.is_a?(Class)
395
535
 
396
536
  raise ArgumentError,
397
537
  "Invalid type: #{type}, must be a Symbol, String or a Class"
398
538
  end
399
539
 
400
- def polymorphic_enabled?
401
- options[:polymorphic]&.any?
540
+ def valid_polymorphic_type?(value, resolved_type)
541
+ return value.is_a?(type) unless has_polymorphic_list?
542
+
543
+ options[:polymorphic].include?(value.class) && value.is_a?(resolved_type)
402
544
  end
403
545
 
404
- def valid_polymorphic_type?(value)
405
- value.is_a?(type) && options[:polymorphic].include?(value.class)
546
+ def has_polymorphic_list?
547
+ options[:polymorphic]&.is_a?(Array)
406
548
  end
407
549
  end
408
550
  end
@@ -41,8 +41,28 @@ module Lutaml
41
41
  raise Lutaml::Model::ChoiceLowerBoundError.new(validated_attributes, @min) if valid.count < @min
42
42
  end
43
43
 
44
+ def import_model_attributes(model)
45
+ return import_model_later(model, :import_model_attributes) if later_importable?(model)
46
+
47
+ root_model_error(model)
48
+ imported_attributes = Utils.deep_dup(model.attributes.values)
49
+ imported_attributes.each do |attr|
50
+ attr.options[:choice] = self
51
+ @model.define_attribute_methods(attr)
52
+ end
53
+ @attributes.concat(imported_attributes)
54
+ attrs_hash = imported_attributes.to_h { |attr| [attr.name, attr] }
55
+ @model.attributes.merge!(attrs_hash)
56
+ end
57
+
44
58
  private
45
59
 
60
+ def root_model_error(model)
61
+ return unless model.root?
62
+
63
+ raise Lutaml::Model::ImportModelWithRootError.new(model)
64
+ end
65
+
46
66
  def valid_attributes(object, validated_attributes)
47
67
  @attributes.each do |attribute|
48
68
  if attribute.is_a?(Choice)
@@ -58,6 +78,16 @@ module Lutaml
58
78
 
59
79
  validated_attributes
60
80
  end
81
+
82
+ def later_importable?(model)
83
+ model.is_a?(Symbol) || model.is_a?(String)
84
+ end
85
+
86
+ def import_model_later(model, method)
87
+ @model.importable_choices[self][method] << model.to_sym
88
+ @model.instance_variable_set(:@choices_imported, false)
89
+ @model.setup_trace_point
90
+ end
61
91
  end
62
92
  end
63
93
  end