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
@@ -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,17 @@ 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
+ instance = if model_class.include?(Lutaml::Model::Serialize)
6
+ model_class.new({}, register: register)
7
+ else
8
+ model_class.new(register)
9
+ end
6
10
  mappings = extract_mappings(options, format)
7
11
 
8
- mappings.each do |rule|
12
+ Utils.add_if_present(options, :key_mappings, mappings.key_mappings)
13
+ Utils.add_if_present(options, :value_mappings, mappings.value_mappings)
14
+
15
+ mappings.mappings.each do |rule|
9
16
  process_mapping_rule(data, instance, format, rule, options)
10
17
  end
11
18
 
@@ -13,18 +20,30 @@ module Lutaml
13
20
  end
14
21
 
15
22
  def model_to_data(instance, format, options = {})
16
- mappings = mappings_for(format).mappings
23
+ mappings = extract_mappings(options, format)
17
24
 
18
- mappings.each_with_object({}) do |rule, hash|
25
+ hash = {}
26
+ mappings.mappings.each do |rule|
19
27
  next unless valid_mapping?(rule, options)
20
- next handle_delegate(instance, rule, hash, format) if rule.delegate
21
28
 
22
- process_mapping_for_instance(instance, hash, format, rule, options)
29
+ process_rule!(instance, rule, hash, format, mappings, options)
23
30
  end
31
+
32
+ hash.keys == [""] ? hash[""] : hash
24
33
  end
25
34
 
26
35
  private
27
36
 
37
+ def process_rule!(instance, rule, hash, format, mappings, options)
38
+ return handle_delegate(instance, rule, hash, format) if rule.delegate
39
+
40
+ value = process_mapping_for_instance(instance, hash, format, rule, options)
41
+
42
+ return unless mappings.key_value_mappings? && value
43
+
44
+ hash[rule_from_name(rule)] = handle_key_value_mappings(value, mappings)
45
+ end
46
+
28
47
  def process_mapping_for_instance(instance, hash, format, rule, options)
29
48
  if rule.custom_methods[:to]
30
49
  return instance.send(rule.custom_methods[:to], instance, hash)
@@ -74,9 +93,11 @@ module Lutaml
74
93
  end
75
94
 
76
95
  def serialize_value(value, rule, attr, format, options)
77
- return attr.serialize(value, format, options) unless rule.child_mappings
96
+ return attr.serialize(value, format, register, options) unless rule.child_mappings
78
97
 
79
- generate_hash_from_child_mappings(attr, value, format, rule.child_mappings)
98
+ generate_hash_from_child_mappings(
99
+ attr, value, format, rule.child_mappings
100
+ )
80
101
  end
81
102
 
82
103
  def rule_from_name(rule)
@@ -90,8 +111,9 @@ module Lutaml
90
111
 
91
112
  generate_remaining_mappings_for_value(child_mappings, value, format)
92
113
 
114
+ attr_type = attr.type(register)
93
115
  value.each do |child_obj|
94
- rules = attr.type.mappings_for(format)
116
+ rules = attr_type.mappings_for(format)
95
117
 
96
118
  hash.merge!(
97
119
  extract_hash_for_child_mapping(child_mappings, child_obj, rules),
@@ -119,7 +141,7 @@ module Lutaml
119
141
  end
120
142
 
121
143
  def child_mapping_for(name, mappings)
122
- mappings.find_by_to(name)&.name.to_s || name.to_s
144
+ mappings.find_by_to(name)&.name.to_s
123
145
  end
124
146
 
125
147
  def extract_hash_for_child_mapping(child_mappings, child_obj, rules)
@@ -167,7 +189,7 @@ module Lutaml
167
189
  return if value.nil? && !rule.render_nil
168
190
 
169
191
  attribute = instance.send(rule.delegate).class.attributes[rule.to]
170
- hash[rule_from_name(rule)] = attribute.serialize(value, format)
192
+ hash[rule_from_name(rule)] = attribute.serialize(value, format, register)
171
193
  end
172
194
 
173
195
  def extract_value_for_delegate(instance, rule)
@@ -175,16 +197,16 @@ module Lutaml
175
197
  end
176
198
 
177
199
  def extract_mappings(options, format)
178
- options[:mappings] || mappings_for(format).mappings
200
+ options[:mappings] || mappings_for(format)
179
201
  end
180
202
 
181
203
  def process_mapping_rule(doc, instance, format, rule, options = {})
182
- raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule)
183
-
184
204
  attr = attribute_for_rule(rule)
185
205
  return if attr&.derived?
186
206
 
187
- value = extract_rule_value(doc, rule, format, attr)
207
+ raise "Attribute '#{rule.to}' not found in #{self}" unless valid_rule?(rule, attr)
208
+
209
+ value = rule_value_extractor_class.call(rule, doc, format, attr, register, options)
188
210
  value = apply_value_map(value, rule.value_map(:from, options), attr)
189
211
 
190
212
  return process_custom_method(rule, instance, value) if rule.has_custom_method_for_deserialization?
@@ -196,37 +218,6 @@ module Lutaml
196
218
  rule.deserialize(instance, value, attributes, self)
197
219
  end
198
220
 
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
221
  def process_custom_method(rule, instance, value)
231
222
  return unless Utils.present?(value)
232
223
 
@@ -235,7 +226,7 @@ module Lutaml
235
226
 
236
227
  def cast_value(value, attr, format, rule)
237
228
  cast_options = rule.polymorphic ? { polymorphic: rule.polymorphic } : {}
238
- attr.cast(value, format, cast_options)
229
+ attr.cast(value, format, register, cast_options)
239
230
  end
240
231
 
241
232
  def translate_mappings(hash, child_mappings, attr, format)
@@ -257,9 +248,10 @@ module Lutaml
257
248
  end
258
249
 
259
250
  def build_child_hash(key, value, child_mappings, attr, format)
251
+ attr_type = attr.type(register)
260
252
  child_mappings.to_h do |attr_name, path|
261
253
  attr_value = extract_attr_value(path, key, value)
262
- attr_rule = attr.type.mappings_for(format).find_by_to(attr_name)
254
+ attr_rule = attr_type.mappings_for(format).find_by_to(attr_name)
263
255
  [attr_rule.from.to_s, attr_value]
264
256
  end
265
257
  end
@@ -279,13 +271,31 @@ module Lutaml
279
271
  end
280
272
 
281
273
  def map_child_data(child_hash, attr, format)
274
+ attr_type = attr.type(register)
282
275
  self.class.data_to_model(
283
- attr.type,
276
+ attr_type,
284
277
  child_hash,
285
278
  format,
286
- { mappings: attr.type.mappings_for(format).mappings },
279
+ { mappings: attr_type.mappings_for(format) },
287
280
  )
288
281
  end
282
+
283
+ def handle_key_value_mappings(value, mappings)
284
+ value.to_h do |v|
285
+ [
286
+ v[mappings.key_mappings.to_instance.to_s],
287
+ if mappings.value_mappings
288
+ v[mappings.value_mappings.as_attribute.to_s]
289
+ else
290
+ v.except(mappings.key_mappings.to_instance.to_s)
291
+ end,
292
+ ]
293
+ end
294
+ end
295
+
296
+ def rule_value_extractor_class
297
+ Lutaml::Model::RuleValueExtractor
298
+ end
289
299
  end
290
300
  end
291
301
  end