lutaml-model 0.7.3 → 0.7.6

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 (185) 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_todo.yml +25 -52
  10. data/README.adoc +2294 -192
  11. data/docs/custom_registers.adoc +228 -0
  12. data/docs/schema_generation.adoc +898 -0
  13. data/docs/schema_import.adoc +364 -0
  14. data/flake.lock +114 -0
  15. data/flake.nix +103 -0
  16. data/lib/lutaml/model/attribute.rb +230 -94
  17. data/lib/lutaml/model/choice.rb +30 -0
  18. data/lib/lutaml/model/collection.rb +195 -0
  19. data/lib/lutaml/model/comparable_model.rb +3 -3
  20. data/lib/lutaml/model/config.rb +26 -3
  21. data/lib/lutaml/model/constants.rb +2 -0
  22. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  23. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  24. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  25. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  26. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  27. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  28. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  29. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  30. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  31. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  32. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  33. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  34. data/lib/lutaml/model/error.rb +10 -0
  35. data/lib/lutaml/model/errors.rb +36 -0
  36. data/lib/lutaml/model/format_registry.rb +5 -2
  37. data/lib/lutaml/model/global_register.rb +41 -0
  38. data/lib/lutaml/model/{hash.rb → hash_adapter.rb} +5 -5
  39. data/lib/lutaml/model/jsonl/document.rb +14 -0
  40. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  41. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  42. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  43. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  44. data/lib/lutaml/model/jsonl.rb +21 -0
  45. data/lib/lutaml/model/key_value_document.rb +3 -2
  46. data/lib/lutaml/model/mapping/key_value_mapping.rb +64 -4
  47. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +4 -0
  48. data/lib/lutaml/model/mapping/mapping_rule.rb +8 -3
  49. data/lib/lutaml/model/register.rb +105 -0
  50. data/lib/lutaml/model/registrable.rb +6 -0
  51. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  52. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  53. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  54. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  55. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  56. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  57. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  58. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  59. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  60. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  61. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  62. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  63. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  64. data/lib/lutaml/model/schema/renderer.rb +36 -0
  65. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  66. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  67. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  68. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  69. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  70. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  71. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  72. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  73. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  74. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  75. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  76. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  77. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  78. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  79. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  80. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  81. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  82. data/lib/lutaml/model/schema.rb +1 -0
  83. data/lib/lutaml/model/sequence.rb +60 -30
  84. data/lib/lutaml/model/serialize.rb +177 -54
  85. data/lib/lutaml/model/services/base.rb +11 -0
  86. data/lib/lutaml/model/services/logger.rb +2 -2
  87. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  88. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  89. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  90. data/lib/lutaml/model/services/type/validator.rb +43 -0
  91. data/lib/lutaml/model/services/validator.rb +145 -0
  92. data/lib/lutaml/model/services.rb +3 -0
  93. data/lib/lutaml/model/transform/key_value_transform.rb +68 -62
  94. data/lib/lutaml/model/transform/xml_transform.rb +46 -57
  95. data/lib/lutaml/model/transform.rb +23 -8
  96. data/lib/lutaml/model/type/boolean.rb +1 -1
  97. data/lib/lutaml/model/type/date.rb +1 -1
  98. data/lib/lutaml/model/type/date_time.rb +1 -1
  99. data/lib/lutaml/model/type/decimal.rb +11 -9
  100. data/lib/lutaml/model/type/float.rb +2 -1
  101. data/lib/lutaml/model/type/integer.rb +24 -21
  102. data/lib/lutaml/model/type/string.rb +4 -2
  103. data/lib/lutaml/model/type/time.rb +1 -1
  104. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  105. data/lib/lutaml/model/type/value.rb +5 -1
  106. data/lib/lutaml/model/type.rb +5 -2
  107. data/lib/lutaml/model/utils.rb +30 -8
  108. data/lib/lutaml/model/validation.rb +6 -4
  109. data/lib/lutaml/model/version.rb +1 -1
  110. data/lib/lutaml/model/xml/document.rb +37 -19
  111. data/lib/lutaml/model/xml/mapping.rb +74 -13
  112. data/lib/lutaml/model/xml/mapping_rule.rb +10 -2
  113. data/lib/lutaml/model/xml/nokogiri_adapter.rb +5 -3
  114. data/lib/lutaml/model/xml/oga/element.rb +4 -1
  115. data/lib/lutaml/model/xml/oga_adapter.rb +4 -3
  116. data/lib/lutaml/model/xml/ox_adapter.rb +20 -6
  117. data/lib/lutaml/model/xml/xml_element.rb +3 -28
  118. data/lib/lutaml/model/xml_adapter/element.rb +1 -1
  119. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +1 -1
  120. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +1 -1
  121. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +1 -1
  122. data/lib/lutaml/model/yamls/document.rb +14 -0
  123. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  124. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  125. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  126. data/lib/lutaml/model/yamls/transform.rb +19 -0
  127. data/lib/lutaml/model/yamls.rb +21 -0
  128. data/lib/lutaml/model.rb +7 -31
  129. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +4 -5
  130. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  131. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  132. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  133. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  134. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  135. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  136. data/spec/lutaml/model/attribute_spec.rb +41 -44
  137. data/spec/lutaml/model/choice_spec.rb +44 -0
  138. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  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 +185 -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/root_mappings/nested_child_mappings_spec.rb +164 -0
  171. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  172. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +6 -6
  173. data/spec/lutaml/model/xml_adapter_spec.rb +24 -0
  174. data/spec/lutaml/model/xml_mapping_rule_spec.rb +11 -4
  175. data/spec/lutaml/model/xml_mapping_spec.rb +1 -1
  176. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  177. data/spec/lutaml/model/yamls_spec.rb +294 -0
  178. data/spec/spec_helper.rb +1 -0
  179. metadata +106 -9
  180. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  181. /data/lib/lutaml/model/{hash → hash_adapter}/document.rb +0 -0
  182. /data/lib/lutaml/model/{hash → hash_adapter}/mapping.rb +0 -0
  183. /data/lib/lutaml/model/{hash → hash_adapter}/mapping_rule.rb +0 -0
  184. /data/lib/lutaml/model/{hash → hash_adapter}/standard_adapter.rb +0 -0
  185. /data/lib/lutaml/model/{hash → hash_adapter}/transform.rb +0 -0
@@ -0,0 +1,11 @@
1
+ module Lutaml
2
+ module Model
3
+ module Services
4
+ class Base
5
+ def self.call(*args)
6
+ new(*args).call
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -11,7 +11,7 @@ module Lutaml
11
11
  # Outputs a warning message that
12
12
  # Usage of `old` name is deprecated will be removed in the next major
13
13
  # release. Please use the `replacement`` instead.
14
- def self.warn_future_deprication(old:, replacement:)
14
+ def self.warn_future_deprecation(old:, replacement:)
15
15
  warn("Usage of `#{old}` is deprecated and will be removed in the next major release. Please use `#{replacement}` instead.")
16
16
  end
17
17
 
@@ -23,7 +23,7 @@ module Lutaml
23
23
  # `<name>` is handled by default. No need to explicitly
24
24
  # define at `<caller_file>:<caller_line>`.
25
25
  def self.warn_auto_handling(name:, caller_file:, caller_line:)
26
- warn("`#{name}` is handled by default. No need to explecitly define at `#{caller_file}:#{caller_line}`")
26
+ warn("`#{name}` is handled by default. No need to explicitly define at `#{caller_file}:#{caller_line}`")
27
27
  end
28
28
 
29
29
  def call(message, type)
@@ -0,0 +1,92 @@
1
+ require_relative "base"
2
+
3
+ module Lutaml
4
+ module Model
5
+ class RuleValueExtractor < Services::Base
6
+ def initialize(rule, doc, format, attr, register, options)
7
+ super()
8
+
9
+ @rule = rule
10
+ @doc = doc
11
+ @format = format
12
+ @attr = attr
13
+ @register = register
14
+ @options = options
15
+ end
16
+
17
+ def call
18
+ rule_names.each do |rule_name|
19
+ value = rule_value_for(rule_name)
20
+ value = transform_mapped_value(value) if should_transform_value?(value)
21
+ return value if Utils.initialized?(value)
22
+ end
23
+
24
+ uninitialized_value
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :rule, :doc, :format, :attr, :register, :options
30
+
31
+ def rule_names
32
+ rule.multiple_mappings? ? rule.name : [rule.name]
33
+ end
34
+
35
+ def rule_value_for(name)
36
+ return doc if root_or_nil?(name)
37
+ return convert_to_format(doc, format) if rule.raw_mapping?
38
+ return fetch_value(name) if Utils.string_or_symbol_key?(doc, name)
39
+ return attr.default(register) if attr&.default_set?(register)
40
+
41
+ uninitialized_value
42
+ end
43
+
44
+ def root_or_nil?(name)
45
+ name.nil? || rule.root_mapping?
46
+ end
47
+
48
+ def fetch_value(name)
49
+ Utils.fetch_str_or_sym(doc, name)
50
+ end
51
+
52
+ def transform_mapped_value(value)
53
+ value.map do |k, v|
54
+ if v.is_a?(Hash)
55
+ transform_hash_value(v, k, options)
56
+ else
57
+ transform_simple_value(k, v, options)
58
+ end
59
+ end
60
+ end
61
+
62
+ def transform_hash_value(hash_value, key, options)
63
+ hash_value.merge(
64
+ {
65
+ options[:key_mappings].to_instance.to_s => key,
66
+ },
67
+ )
68
+ end
69
+
70
+ def transform_simple_value(key, value, options)
71
+ {
72
+ options[:key_mappings].to_instance.to_s => key,
73
+ options[:value_mappings].as_attribute.to_s => value,
74
+ }
75
+ end
76
+
77
+ def should_transform_value?(value)
78
+ (options[:key_mappings] || options[:value_mappings]) &&
79
+ value.is_a?(Hash)
80
+ end
81
+
82
+ def convert_to_format(doc, format)
83
+ adapter = Lutaml::Model::Config.adapter_for(format)
84
+ adapter.new(doc).public_send(:"to_#{format}")
85
+ end
86
+
87
+ def uninitialized_value
88
+ Lutaml::Model::UninitializedClass.instance
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Services
6
+ module Type
7
+ class Validator
8
+ class Number < Validator
9
+ module ClassMethods
10
+ def validate!(value, options)
11
+ return if Utils.blank?(options)
12
+
13
+ validate_values!(value, options[:values])
14
+ validate_min_max_bounds!(value, options)
15
+ end
16
+ end
17
+
18
+ extend ClassMethods
19
+ include ClassMethods
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,52 @@
1
+ module Lutaml
2
+ module Model
3
+ module Services
4
+ module Type
5
+ class Validator
6
+ class String < Validator
7
+ module ClassMethods
8
+ def validate!(value, options)
9
+ return if Utils.blank?(value)
10
+
11
+ validate_values!(value, options[:values])
12
+ validate_length!(value, options)
13
+ validate_pattern!(value, options)
14
+ end
15
+
16
+ def validate_length!(value, options)
17
+ min, max = options&.values_at(:min, :max)
18
+ return if min.nil? || max.nil?
19
+
20
+ validate_min_length!(value, min) if min
21
+ validate_max_length!(value, max) if max
22
+ end
23
+
24
+ def validate_pattern!(value, options)
25
+ pattern = options[:pattern]
26
+ return if Utils.blank?(pattern)
27
+ return if value.match?(pattern)
28
+
29
+ raise Lutaml::Model::Type::PatternNotMatchedError.new(value, pattern)
30
+ end
31
+
32
+ def validate_min_length!(value, min)
33
+ return if value.length >= min
34
+
35
+ raise Lutaml::Model::Type::MinLengthError.new(value, min)
36
+ end
37
+
38
+ def validate_max_length!(value, max)
39
+ return if value.length <= max
40
+
41
+ raise Lutaml::Model::Type::MaxLengthError.new(value, max)
42
+ end
43
+ end
44
+
45
+ extend ClassMethods
46
+ include ClassMethods
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ require_relative "validator/string"
2
+ require_relative "validator/number"
3
+
4
+ module Lutaml
5
+ module Model
6
+ module Services
7
+ module Type
8
+ class Validator
9
+ module ClassMethods
10
+ def validate_values!(value, values)
11
+ return if Utils.blank?(values) || values.include?(value)
12
+
13
+ raise Lutaml::Model::Type::InvalidValueError.new(value, values)
14
+ end
15
+
16
+ def validate_min_max_bounds!(value, options)
17
+ min, max = options&.values_at(:min, :max)
18
+ return if min.nil? && max.nil?
19
+
20
+ validate_min_bound!(value, min) if min
21
+ validate_max_bound!(value, max) if max
22
+ end
23
+
24
+ def validate_min_bound!(value, min)
25
+ return if value >= min
26
+
27
+ raise Lutaml::Model::Type::MinBoundError.new(value, min)
28
+ end
29
+
30
+ def validate_max_bound!(value, max)
31
+ return if value <= max
32
+
33
+ raise Lutaml::Model::Type::MaxBoundError.new(value, max)
34
+ end
35
+ end
36
+
37
+ extend ClassMethods
38
+ include ClassMethods
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,145 @@
1
+ require_relative "base"
2
+
3
+ module Lutaml
4
+ module Model
5
+ class ValidationRule
6
+ attr_reader :attribute, :options, :custom_method
7
+
8
+ def initialize(attribute: nil, custom_method: nil, options: {})
9
+ if Utils.blank?(attribute) && Utils.blank?(custom_method)
10
+ raise ArgumentError, "Missing attribute or custom method"
11
+ end
12
+
13
+ @attribute = attribute
14
+ @options = options
15
+ @custom_method = custom_method
16
+ end
17
+
18
+ def custom?
19
+ Utils.present?(custom_method)
20
+ end
21
+
22
+ def has_options?
23
+ options.is_a?(Hash)
24
+ end
25
+ end
26
+
27
+ class Validator < Services::Base
28
+ attr_reader :value, :validations, :errors, :memoization_container
29
+
30
+ def initialize(value, validations, memoization_container)
31
+ super()
32
+
33
+ @errors = Errors.new
34
+ @value = value
35
+ @memoization_container = memoization_container
36
+
37
+ resolve_validations(validations)
38
+ end
39
+
40
+ def call
41
+ return [] if Utils.blank?(validations)
42
+
43
+ @validations.each do |validation|
44
+ if validation.custom?
45
+ public_send(validation.custom_method, value)
46
+ else
47
+ validation.options.each do |key, rule|
48
+ send(:"validate_#{key}", value, validation.attribute, rule)
49
+ end
50
+ end
51
+ end
52
+
53
+ errors.messages
54
+ end
55
+
56
+ private
57
+
58
+ def resolve_validations(validations_block)
59
+ @validations ||= []
60
+ return unless validations_block
61
+
62
+ instance_eval(&validations_block)
63
+ end
64
+
65
+ def validate(method_name)
66
+ @validations << ValidationRule.new(custom_method: method_name)
67
+ end
68
+
69
+ def validates(attribute, options)
70
+ @validations <<
71
+ ValidationRule.new(attribute: attribute, options: options)
72
+ end
73
+
74
+ def validate_presence(model, attr, rule)
75
+ return if rule.nil?
76
+
77
+ if model.is_a?(Array)
78
+ model.each { |v| validate_presence(v, attr, rule) }
79
+ return
80
+ end
81
+
82
+ value = model.public_send(attr)
83
+ return if Utils.present?(value)
84
+
85
+ errors.add(attr, "`#{attr}` is required")
86
+ end
87
+
88
+ def validate_numericality(model, attr, rule)
89
+ return if rule.nil?
90
+
91
+ if model.is_a?(Array)
92
+ model.each { |v| validate_numericality(v, attr, rule) }
93
+ return
94
+ end
95
+
96
+ value = model.public_send(attr)
97
+ return unless validate_integer(attr, value)
98
+
99
+ validate_comparison_rules(attr, value, rule)
100
+ end
101
+
102
+ def validate_integer(attr, value)
103
+ return true if value.is_a?(Integer)
104
+
105
+ errors.add(
106
+ attr,
107
+ "`#{attr}` value is `#{value.class}`, but expected integer",
108
+ )
109
+ end
110
+
111
+ def validate_comparison_rules(attr, value, options)
112
+ validate_less_than(attr, value, options[:less_than])
113
+ validate_greater_than(attr, value, options[:greater_than])
114
+ validate_equal_to(attr, value, options[:equal_to])
115
+ end
116
+
117
+ def validate_less_than(attr, value, limit)
118
+ return if !limit || value < limit
119
+
120
+ errors.add(
121
+ attr,
122
+ "#{attr} value is `#{value}`, which is not less than #{limit}",
123
+ )
124
+ end
125
+
126
+ def validate_greater_than(attr, value, limit)
127
+ return if !limit || value > limit
128
+
129
+ errors.add(
130
+ attr,
131
+ "#{attr} value is `#{value}`, which is not greater than #{limit}",
132
+ )
133
+ end
134
+
135
+ def validate_equal_to(attr, value, target)
136
+ return if !target || value == target
137
+
138
+ errors.add(
139
+ attr,
140
+ "#{attr} value is `#{value}`, which is not equal to #{target}",
141
+ )
142
+ end
143
+ end
144
+ end
145
+ end
@@ -1,2 +1,5 @@
1
1
  require_relative "services/logger"
2
+ require_relative "services/rule_value_extractor"
2
3
  require_relative "services/transformer"
4
+ require_relative "services/validator"
5
+ require_relative "services/type/validator"
@@ -2,10 +2,18 @@ module Lutaml
2
2
  module Model
3
3
  class KeyValueTransform < Lutaml::Model::Transform
4
4
  def data_to_model(data, format, options = {})
5
- instance = model_class.new
5
+ if model_class.include?(Lutaml::Model::Serialize)
6
+ instance = model_class.new(__register: __register)
7
+ else
8
+ instance = model_class.new
9
+ register_accessor_methods_for(instance, __register)
10
+ end
6
11
  mappings = extract_mappings(options, format)
7
12
 
8
- mappings.each do |rule|
13
+ Utils.add_if_present(options, :key_mappings, mappings.key_mappings)
14
+ Utils.add_if_present(options, :value_mappings, mappings.value_mappings)
15
+
16
+ mappings.mappings.each do |rule|
9
17
  process_mapping_rule(data, instance, format, rule, options)
10
18
  end
11
19
 
@@ -13,18 +21,30 @@ module Lutaml
13
21
  end
14
22
 
15
23
  def model_to_data(instance, format, options = {})
16
- mappings = mappings_for(format).mappings
24
+ mappings = extract_mappings(options, format)
17
25
 
18
- mappings.each_with_object({}) do |rule, hash|
26
+ hash = {}
27
+ mappings.mappings.each do |rule|
19
28
  next unless valid_mapping?(rule, options)
20
- next handle_delegate(instance, rule, hash, format) if rule.delegate
21
29
 
22
- process_mapping_for_instance(instance, hash, format, rule, options)
30
+ process_rule!(instance, rule, hash, format, mappings, options)
23
31
  end
32
+
33
+ hash.keys == [""] ? hash[""] : hash
24
34
  end
25
35
 
26
36
  private
27
37
 
38
+ def process_rule!(instance, rule, hash, format, mappings, options)
39
+ return handle_delegate(instance, rule, hash, format) if rule.delegate
40
+
41
+ value = process_mapping_for_instance(instance, hash, format, rule, options)
42
+
43
+ return unless mappings.key_value_mappings? && value
44
+
45
+ hash[rule_from_name(rule)] = handle_key_value_mappings(value, mappings)
46
+ end
47
+
28
48
  def process_mapping_for_instance(instance, hash, format, rule, options)
29
49
  if rule.custom_methods[:to]
30
50
  return instance.send(rule.custom_methods[:to], instance, hash)
@@ -74,9 +94,11 @@ module Lutaml
74
94
  end
75
95
 
76
96
  def serialize_value(value, rule, attr, format, options)
77
- return attr.serialize(value, format, options) unless rule.child_mappings
97
+ return attr.serialize(value, format, __register, options) unless rule.child_mappings
78
98
 
79
- generate_hash_from_child_mappings(attr, value, format, rule.child_mappings)
99
+ generate_hash_from_child_mappings(
100
+ attr, value, format, rule.child_mappings
101
+ )
80
102
  end
81
103
 
82
104
  def rule_from_name(rule)
@@ -90,11 +112,12 @@ module Lutaml
90
112
 
91
113
  generate_remaining_mappings_for_value(child_mappings, value, format)
92
114
 
115
+ attr_type = attr.type(__register)
93
116
  value.each do |child_obj|
94
- rules = attr.type.mappings_for(format)
117
+ rules = attr_type.mappings_for(format)
95
118
 
96
119
  hash.merge!(
97
- extract_hash_for_child_mapping(child_mappings, child_obj, rules),
120
+ extract_hash_for_child_mapping(child_mappings, child_obj, rules, format),
98
121
  )
99
122
  end
100
123
 
@@ -119,17 +142,16 @@ module Lutaml
119
142
  end
120
143
 
121
144
  def child_mapping_for(name, mappings)
122
- mappings.find_by_to(name)&.name.to_s || name.to_s
145
+ mappings.find_by_to(name)&.name.to_s
123
146
  end
124
147
 
125
- def extract_hash_for_child_mapping(child_mappings, child_obj, rules)
148
+ def extract_hash_for_child_mapping(child_mappings, child_obj, rules, format)
126
149
  key = nil
127
150
  value = {}
128
151
 
129
152
  child_mappings.each do |attr_name, path|
130
153
  rule = rules.find_by_to(attr_name)
131
-
132
- attr_value = normalize_attribute_value(child_obj.send(attr_name))
154
+ attr_value = normalize_attribute_value(child_obj, rule.from, format)
133
155
 
134
156
  next unless rule&.render?(attr_value, nil)
135
157
  next key = attr_value if path == :key
@@ -141,14 +163,10 @@ module Lutaml
141
163
  { key => value }
142
164
  end
143
165
 
144
- def normalize_attribute_value(value)
145
- if value.is_a?(Lutaml::Model::Serialize)
146
- value.to_hash
147
- elsif value.is_a?(Array) && value.first.is_a?(Lutaml::Model::Serialize)
148
- value.map(&:to_hash)
149
- else
150
- value
151
- end
166
+ def normalize_attribute_value(value, attr_name, format)
167
+ Lutaml::Model::Config.adapter_for(format).parse(
168
+ value.public_send(:"to_#{format}"),
169
+ )[attr_name.to_s]
152
170
  end
153
171
 
154
172
  def extract_hash_value_for_child_mapping(path, value, map_value)
@@ -167,7 +185,7 @@ module Lutaml
167
185
  return if value.nil? && !rule.render_nil
168
186
 
169
187
  attribute = instance.send(rule.delegate).class.attributes[rule.to]
170
- hash[rule_from_name(rule)] = attribute.serialize(value, format)
188
+ hash[rule_from_name(rule)] = attribute.serialize(value, format, __register)
171
189
  end
172
190
 
173
191
  def extract_value_for_delegate(instance, rule)
@@ -175,16 +193,16 @@ module Lutaml
175
193
  end
176
194
 
177
195
  def extract_mappings(options, format)
178
- options[:mappings] || mappings_for(format).mappings
196
+ options[:mappings] || mappings_for(format)
179
197
  end
180
198
 
181
199
  def process_mapping_rule(doc, instance, format, rule, options = {})
182
- raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
183
-
184
200
  attr = attribute_for_rule(rule)
185
201
  return if attr&.derived?
186
202
 
187
- value = extract_rule_value(doc, rule, format, attr)
203
+ raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule, attr)
204
+
205
+ value = rule_value_extractor_class.call(rule, doc, format, attr, __register, options)
188
206
  value = apply_value_map(value, rule.value_map(:from, options), attr)
189
207
 
190
208
  return process_custom_method(rule, instance, value) if rule.has_custom_method_for_deserialization?
@@ -196,37 +214,6 @@ module Lutaml
196
214
  rule.deserialize(instance, value, attributes, self)
197
215
  end
198
216
 
199
- def extract_rule_value(doc, rule, format, attr)
200
- rule_names = rule.multiple_mappings? ? rule.name : [rule.name]
201
-
202
- rule_names.each do |rule_name|
203
- value = rule_value_for(rule_name, doc, rule, format, attr)
204
-
205
- return value if Utils.initialized?(value)
206
- end
207
-
208
- Lutaml::Model::UninitializedClass.instance
209
- end
210
-
211
- def rule_value_for(name, doc, rule, format, attr)
212
- if rule.root_mapping?
213
- doc
214
- elsif rule.raw_mapping?
215
- convert_to_format(doc, format)
216
- elsif Utils.string_or_symbol_key?(doc, name)
217
- Utils.fetch_with_string_or_symbol_key(doc, name)
218
- elsif attr&.default_set?
219
- attr.default
220
- else
221
- Lutaml::Model::UninitializedClass.instance
222
- end
223
- end
224
-
225
- def convert_to_format(doc, format)
226
- adapter = Lutaml::Model::Config.adapter_for(format)
227
- adapter.new(doc).public_send(:"to_#{format}")
228
- end
229
-
230
217
  def process_custom_method(rule, instance, value)
231
218
  return unless Utils.present?(value)
232
219
 
@@ -235,7 +222,7 @@ module Lutaml
235
222
 
236
223
  def cast_value(value, attr, format, rule)
237
224
  cast_options = rule.polymorphic ? { polymorphic: rule.polymorphic } : {}
238
- attr.cast(value, format, cast_options)
225
+ attr.cast(value, format, __register, cast_options)
239
226
  end
240
227
 
241
228
  def translate_mappings(hash, child_mappings, attr, format)
@@ -257,9 +244,10 @@ module Lutaml
257
244
  end
258
245
 
259
246
  def build_child_hash(key, value, child_mappings, attr, format)
247
+ attr_type = attr.type(__register)
260
248
  child_mappings.to_h do |attr_name, path|
261
249
  attr_value = extract_attr_value(path, key, value)
262
- attr_rule = attr.type.mappings_for(format).find_by_to(attr_name)
250
+ attr_rule = attr_type.mappings_for(format).find_by_to(attr_name)
263
251
  [attr_rule.from.to_s, attr_value]
264
252
  end
265
253
  end
@@ -279,13 +267,31 @@ module Lutaml
279
267
  end
280
268
 
281
269
  def map_child_data(child_hash, attr, format)
270
+ attr_type = attr.type(__register)
282
271
  self.class.data_to_model(
283
- attr.type,
272
+ attr_type,
284
273
  child_hash,
285
274
  format,
286
- { mappings: attr.type.mappings_for(format).mappings },
275
+ { mappings: attr_type.mappings_for(format) },
287
276
  )
288
277
  end
278
+
279
+ def handle_key_value_mappings(value, mappings)
280
+ value.to_h do |v|
281
+ [
282
+ v[mappings.key_mappings.to_instance.to_s],
283
+ if mappings.value_mappings
284
+ v[mappings.value_mappings.as_attribute.to_s]
285
+ else
286
+ v.except(mappings.key_mappings.to_instance.to_s)
287
+ end,
288
+ ]
289
+ end
290
+ end
291
+
292
+ def rule_value_extractor_class
293
+ Lutaml::Model::RuleValueExtractor
294
+ end
289
295
  end
290
296
  end
291
297
  end