lutaml-model 0.7.3 → 0.7.5

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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.github/workflows/release.yml +3 -0
  4. data/.gitignore +6 -1
  5. data/.irbrc +1 -0
  6. data/.pryrc +1 -0
  7. data/.rubocop_todo.yml +25 -52
  8. data/README.adoc +2177 -192
  9. data/docs/custom_registers.adoc +228 -0
  10. data/docs/schema_generation.adoc +898 -0
  11. data/docs/schema_import.adoc +364 -0
  12. data/flake.lock +114 -0
  13. data/flake.nix +103 -0
  14. data/lib/lutaml/model/attribute.rb +230 -94
  15. data/lib/lutaml/model/choice.rb +30 -0
  16. data/lib/lutaml/model/collection.rb +194 -0
  17. data/lib/lutaml/model/comparable_model.rb +3 -3
  18. data/lib/lutaml/model/config.rb +26 -3
  19. data/lib/lutaml/model/constants.rb +2 -0
  20. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  21. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  22. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  23. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  24. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  25. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  26. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  27. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  28. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  29. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  30. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  31. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  32. data/lib/lutaml/model/error.rb +10 -0
  33. data/lib/lutaml/model/errors.rb +36 -0
  34. data/lib/lutaml/model/format_registry.rb +5 -2
  35. data/lib/lutaml/model/global_register.rb +41 -0
  36. data/lib/lutaml/model/{hash.rb → hash_adapter.rb} +5 -5
  37. data/lib/lutaml/model/jsonl/document.rb +14 -0
  38. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  39. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  40. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  41. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  42. data/lib/lutaml/model/jsonl.rb +21 -0
  43. data/lib/lutaml/model/key_value_document.rb +3 -2
  44. data/lib/lutaml/model/mapping/key_value_mapping.rb +64 -4
  45. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +4 -0
  46. data/lib/lutaml/model/mapping/mapping_rule.rb +8 -3
  47. data/lib/lutaml/model/register.rb +105 -0
  48. data/lib/lutaml/model/registrable.rb +6 -0
  49. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  50. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  51. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  52. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  53. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  54. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  55. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  56. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  57. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  58. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  59. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  60. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  61. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  62. data/lib/lutaml/model/schema/renderer.rb +36 -0
  63. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  64. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  65. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  66. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  67. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  68. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  69. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  70. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  71. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  72. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  73. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  74. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  75. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  76. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  77. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  78. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  79. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  80. data/lib/lutaml/model/schema.rb +1 -0
  81. data/lib/lutaml/model/sequence.rb +60 -30
  82. data/lib/lutaml/model/serialize.rb +175 -53
  83. data/lib/lutaml/model/services/base.rb +11 -0
  84. data/lib/lutaml/model/services/logger.rb +2 -2
  85. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  86. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  87. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  88. data/lib/lutaml/model/services/type/validator.rb +43 -0
  89. data/lib/lutaml/model/services/validator.rb +145 -0
  90. data/lib/lutaml/model/services.rb +3 -0
  91. data/lib/lutaml/model/transform/key_value_transform.rb +60 -50
  92. data/lib/lutaml/model/transform/xml_transform.rb +46 -57
  93. data/lib/lutaml/model/transform.rb +22 -8
  94. data/lib/lutaml/model/type/boolean.rb +1 -1
  95. data/lib/lutaml/model/type/date.rb +1 -1
  96. data/lib/lutaml/model/type/date_time.rb +1 -1
  97. data/lib/lutaml/model/type/decimal.rb +11 -9
  98. data/lib/lutaml/model/type/float.rb +2 -1
  99. data/lib/lutaml/model/type/integer.rb +24 -21
  100. data/lib/lutaml/model/type/string.rb +4 -2
  101. data/lib/lutaml/model/type/time.rb +1 -1
  102. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  103. data/lib/lutaml/model/type/value.rb +5 -1
  104. data/lib/lutaml/model/type.rb +5 -2
  105. data/lib/lutaml/model/utils.rb +30 -8
  106. data/lib/lutaml/model/validation.rb +6 -4
  107. data/lib/lutaml/model/version.rb +1 -1
  108. data/lib/lutaml/model/xml/document.rb +37 -19
  109. data/lib/lutaml/model/xml/mapping.rb +74 -13
  110. data/lib/lutaml/model/xml/mapping_rule.rb +10 -2
  111. data/lib/lutaml/model/xml/nokogiri_adapter.rb +5 -3
  112. data/lib/lutaml/model/xml/oga/element.rb +4 -1
  113. data/lib/lutaml/model/xml/oga_adapter.rb +4 -3
  114. data/lib/lutaml/model/xml/ox_adapter.rb +20 -6
  115. data/lib/lutaml/model/xml/xml_element.rb +3 -28
  116. data/lib/lutaml/model/xml_adapter/element.rb +1 -1
  117. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  118. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  119. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  120. data/lib/lutaml/model/yamls/document.rb +14 -0
  121. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  122. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  123. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  124. data/lib/lutaml/model/yamls/transform.rb +19 -0
  125. data/lib/lutaml/model/yamls.rb +21 -0
  126. data/lib/lutaml/model.rb +7 -31
  127. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -5
  128. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  129. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  130. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  131. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  132. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  133. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  134. data/spec/lutaml/model/attribute_spec.rb +41 -44
  135. data/spec/lutaml/model/choice_spec.rb +44 -0
  136. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  137. data/spec/lutaml/model/custom_model_spec.rb +15 -3
  138. data/spec/lutaml/model/defaults_spec.rb +5 -1
  139. data/spec/lutaml/model/global_register_spec.rb +108 -0
  140. data/spec/lutaml/model/group_spec.rb +9 -3
  141. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  142. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  143. data/spec/lutaml/model/multiple_mapping_spec.rb +1 -1
  144. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  145. data/spec/lutaml/model/register/xml_spec.rb +187 -0
  146. data/spec/lutaml/model/register_spec.rb +147 -0
  147. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  148. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  149. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  150. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  151. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  152. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  153. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  154. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  155. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  156. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  157. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  158. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  159. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  160. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  161. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  162. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  163. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  164. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  165. data/spec/lutaml/model/sequence_spec.rb +36 -0
  166. data/spec/lutaml/model/serializable_spec.rb +31 -0
  167. data/spec/lutaml/model/type_spec.rb +8 -4
  168. data/spec/lutaml/model/utils_spec.rb +3 -3
  169. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  170. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  171. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  172. data/spec/lutaml/model/xml_adapter_spec.rb +24 -0
  173. data/spec/lutaml/model/xml_mapping_rule_spec.rb +11 -4
  174. data/spec/lutaml/model/xml_mapping_spec.rb +1 -1
  175. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  176. data/spec/lutaml/model/yamls_spec.rb +294 -0
  177. data/spec/spec_helper.rb +1 -0
  178. metadata +105 -9
  179. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  180. /data/lib/lutaml/model/{hash → hash_adapter}/document.rb +0 -0
  181. /data/lib/lutaml/model/{hash → hash_adapter}/mapping.rb +0 -0
  182. /data/lib/lutaml/model/{hash → hash_adapter}/mapping_rule.rb +0 -0
  183. /data/lib/lutaml/model/{hash → hash_adapter}/standard_adapter.rb +0 -0
  184. /data/lib/lutaml/model/{hash → hash_adapter}/transform.rb +0 -0
@@ -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)
78
130
 
79
- value.map { |v| type.cast(v) }
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
140
+
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)
268
+ end
269
+
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)
277
+
278
+ return if errors.empty?
279
+
280
+ raise Lutaml::Model::ValidationFailedError.new(errors)
185
281
  end
186
282
 
187
- def validate_polymorphic(value)
188
- return value.all? { |v| validate_polymorphic!(v) } if value.is_a?(Array)
283
+ def validate_polymorphic(value, resolved_type)
284
+ return value.all? { |v| validate_polymorphic!(v, resolved_type) } if value.is_a?(Array)
189
285
  return true unless options[:polymorphic]
190
286
 
191
- valid_polymorphic_type?(value)
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, type)
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,43 +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
 
293
- def serializable?
294
- type <= Serialize
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?
295
421
  end
296
422
 
297
423
  def deep_dup
298
- self.class.new(name, type, Utils.deep_dup(options))
424
+ self.class.new(name, unresolved_type, Utils.deep_dup(options))
299
425
  end
300
426
 
301
427
  private
302
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
+
303
443
  def resolve_polymorphic_class(type, value, options)
304
444
  return type unless polymorphic_map_defined?(options, value)
305
445
 
@@ -320,35 +460,42 @@ module Lutaml
320
460
  (format == :xml && value.is_a?(Lutaml::Model::Xml::XmlElement))
321
461
  end
322
462
 
323
- def castable_serialized_type?(value)
324
- type <= Serialize && value.is_a?(type.model)
325
- end
326
-
327
463
  def can_serialize?(klass, value, format)
328
464
  klass <= Serialize && castable?(value, format)
329
465
  end
330
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
+
331
475
  def needs_conversion?(klass, value)
332
- !value.nil? && !value.is_a?(klass)
476
+ !value.nil? && !value.is_a?(klass) && Utils.initialized?(value)
333
477
  end
334
478
 
335
479
  def already_serialized?(klass, value)
336
480
  klass <= Serialize && value.is_a?(klass.model)
337
481
  end
338
482
 
339
- def serialize_array(value, format, options)
340
- value.map { |v| serialize(v, format, options) }
483
+ def serialize_array(value, format, register, options)
484
+ value.map { |v| serialize(v, format, register, options) }
341
485
  end
342
486
 
343
- def serialize_model(value, format, options)
487
+ def serialize_model(value, format, register, options)
488
+ as_options = options.merge(register: register)
344
489
  return unless Utils.present?(value)
345
- return value.class.as(format, value, options) if value.is_a?(type)
346
490
 
347
- 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)
348
495
  end
349
496
 
350
- def serialize_value(value, format)
351
- 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)
352
499
  value.send(:"to_#{format}")
353
500
  end
354
501
 
@@ -358,29 +505,19 @@ module Lutaml
358
505
  raise ArgumentError, "method or type must be set for an attribute"
359
506
  end
360
507
 
361
- def process_type!(type)
362
- validate_type!(type)
363
- @type = cast_type!(type)
364
- end
365
-
366
- def process_options!
367
- validate_options!(@options)
368
- @raw = !!@options[:raw]
369
- set_default_for_collection if collection?
370
- end
371
-
372
508
  def set_default_for_collection
373
509
  validate_collection_range
374
- @options[:default] ||= -> { [] } if initialize_empty?
510
+ @options[:default] ||= -> { build_collection } if initialize_empty?
375
511
  end
376
512
 
377
513
  def validate_options!(options)
378
514
  if (invalid_opts = options.keys - ALLOWED_OPTIONS).any?
379
- raise StandardError,
380
- "Invalid options given for `#{name}` #{invalid_opts}"
515
+ raise Lutaml::Model::InvalidAttributeOptionsError.new(name, invalid_opts)
381
516
  end
382
517
 
383
- 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)
384
521
  raise StandardError,
385
522
  "Invalid option `pattern` given for `#{name}`, " \
386
523
  "`pattern` is only allowed for :string type"
@@ -394,17 +531,16 @@ module Lutaml
394
531
  end
395
532
 
396
533
  def validate_type!(type)
397
- return true if type.is_a?(Class)
398
- return true if [Symbol, String].include?(type.class) && cast_type!(type)
534
+ return true if [Symbol, String].include?(type.class) || type.is_a?(Class)
399
535
 
400
536
  raise ArgumentError,
401
537
  "Invalid type: #{type}, must be a Symbol, String or a Class"
402
538
  end
403
539
 
404
- def valid_polymorphic_type?(value)
540
+ def valid_polymorphic_type?(value, resolved_type)
405
541
  return value.is_a?(type) unless has_polymorphic_list?
406
542
 
407
- options[:polymorphic].include?(value.class) && value.is_a?(type)
543
+ options[:polymorphic].include?(value.class) && value.is_a?(resolved_type)
408
544
  end
409
545
 
410
546
  def has_polymorphic_list?
@@ -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