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
@@ -4,40 +4,44 @@ module Lutaml
4
4
  module Model
5
5
  module Schema
6
6
  class XsdSchema
7
+ extend SharedMethods
8
+
7
9
  def self.generate(klass, options = {})
10
+ register = extract_register_from(klass)
8
11
  builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
9
12
  xml.schema(xmlns: "http://www.w3.org/2001/XMLSchema") do
10
- generate_complex_type(xml, klass)
13
+ generate_complex_type(xml, klass, nil, register)
11
14
  end
12
15
  end
13
16
 
14
17
  options[:pretty] ? builder.to_xml(indent: 2) : builder.to_xml
15
18
  end
16
19
 
17
- def self.generate_complex_type(xml, klass, element_name = nil)
20
+ def self.generate_complex_type(xml, klass, element_name = nil, register)
18
21
  xml.element(name: element_name || klass.name) do
19
22
  xml.complexType do
20
23
  xml.sequence do
21
- generate_elements(xml, klass)
24
+ generate_elements(xml, klass, register)
22
25
  end
23
26
  end
24
27
  end
25
28
  end
26
29
 
27
- def self.generate_elements(xml, klass)
30
+ def self.generate_elements(xml, klass, register)
28
31
  klass.attributes.each do |name, attr|
29
- if attr.type <= Lutaml::Model::Serialize
30
- generate_complex_type(xml, attr.type, name)
32
+ attr_type = attr.type(register)
33
+ if attr_type <= Lutaml::Model::Serialize
34
+ generate_complex_type(xml, attr_type, name, register)
31
35
  elsif attr.collection?
32
36
  xml.element(name: name, minOccurs: "0", maxOccurs: "unbounded") do
33
37
  xml.complexType do
34
38
  xml.sequence do
35
- xml.element(name: "item", type: get_xsd_type(attr.type))
39
+ xml.element(name: "item", type: get_xsd_type(attr_type))
36
40
  end
37
41
  end
38
42
  end
39
43
  else
40
- xml.element(name: name, type: get_xsd_type(attr.type))
44
+ xml.element(name: name, type: get_xsd_type(attr_type))
41
45
  end
42
46
  end
43
47
  end
@@ -1,49 +1,55 @@
1
+ require_relative "base_schema"
1
2
  require "yaml"
2
3
 
3
4
  module Lutaml
4
5
  module Model
5
6
  module Schema
6
- class YamlSchema
7
- def self.generate(klass, options = {})
8
- schema = generate_schema(klass)
9
- options[:pretty] ? schema.to_yaml : YAML.dump(schema)
10
- end
7
+ class YamlSchema < BaseSchema
8
+ class << self
9
+ def generate(
10
+ klass,
11
+ id: nil,
12
+ title: nil,
13
+ description: nil,
14
+ pretty: false
15
+ )
16
+ options = {
17
+ schema: "https://json-schema.org/draft/2020-12/schema",
18
+ id: id,
19
+ title: title,
20
+ description: description,
21
+ pretty: pretty,
22
+ }
11
23
 
12
- def self.generate_schema(klass)
13
- {
14
- "type" => "map",
15
- "mapping" => generate_mapping(klass),
16
- }
17
- end
24
+ super(klass, options)
25
+ end
18
26
 
19
- def self.generate_mapping(klass)
20
- klass.attributes.each_with_object({}) do |(name, attr), mapping|
21
- mapping[name.to_s] = generate_attribute_schema(attr)
27
+ def format_schema(schema, options)
28
+ <<~SCHEMA
29
+ %YAML 1.1
30
+ #{options[:pretty] ? schema.to_yaml : YAML.dump(schema)}
31
+ SCHEMA
22
32
  end
23
- end
24
33
 
25
- def self.generate_attribute_schema(attr)
26
- if attr.type <= Lutaml::Model::Serialize
27
- generate_schema(attr.type)
28
- elsif attr.collection?
29
- {
30
- "type" => "seq",
31
- "sequence" => [{ "type" => get_yaml_type(attr.type) }],
32
- }
33
- else
34
- { "type" => get_yaml_type(attr.type) }
34
+ def generate_model_classes(schema)
35
+ definitions = definition_collection_class.new(schema["$defs"])
36
+
37
+ definitions.transform_values do |definition|
38
+ generate_model_class(definition)
39
+ end
35
40
  end
36
- end
37
41
 
38
- def self.get_yaml_type(type)
39
- {
40
- Lutaml::Model::Type::String => "str",
41
- Lutaml::Model::Type::Integer => "int",
42
- Lutaml::Model::Type::Boolean => "bool",
43
- Lutaml::Model::Type::Float => "float",
44
- Lutaml::Model::Type::Decimal => "float",
45
- Lutaml::Model::Type::Hash => "map",
46
- }[type] || "str" # Default to string for unknown types
42
+ private
43
+
44
+ def generate_model_class(schema)
45
+ template = File.join(__dir__, "templates", "model.erb")
46
+
47
+ Lutaml::Model::Schema::Renderer.render(template, schema: schema)
48
+ end
49
+
50
+ def definition_collection_class
51
+ Lutaml::Model::Schema::Decorators::DefinitionCollection
52
+ end
47
53
  end
48
54
  end
49
55
  end
@@ -1,3 +1,4 @@
1
+ require_relative "schema/shared_methods"
1
2
  require_relative "schema/json_schema"
2
3
  require_relative "schema/xsd_schema"
3
4
  require_relative "schema/relaxng_schema"
@@ -18,30 +18,16 @@ module Lutaml
18
18
  instance_eval(&block)
19
19
  end
20
20
 
21
- def map_element(
22
- name,
23
- to: nil,
24
- render_nil: false,
25
- render_default: false,
26
- with: {},
27
- delegate: nil,
28
- cdata: false,
29
- namespace: nil,
30
- prefix: nil,
31
- transform: {}
32
- )
33
- @attributes << @model.map_element(
34
- name,
35
- to: to,
36
- render_nil: render_nil,
37
- render_default: render_default,
38
- with: with,
39
- delegate: delegate,
40
- cdata: cdata,
41
- namespace: namespace,
42
- prefix: prefix,
43
- transform: transform,
44
- )
21
+ def map_element(name, **kwargs)
22
+ @attributes << @model.map_element(name, **kwargs)
23
+ end
24
+
25
+ def import_model_mappings(model)
26
+ return import_mappings_later(model) if later_importable?(model)
27
+ raise Lutaml::Model::ImportModelWithRootError.new(model) if model.root?
28
+
29
+ @model.import_model_mappings(model)
30
+ @attributes.concat(Utils.deep_dup(model.mappings_for(:xml).elements))
45
31
  end
46
32
 
47
33
  def map_attribute(*)
@@ -56,16 +42,60 @@ module Lutaml
56
42
  raise Lutaml::Model::UnknownSequenceMappingError.new("map_all")
57
43
  end
58
44
 
59
- def validate_content!(element_order)
60
- defined_order = @attributes.map { |rule| rule.name.to_s }
61
- start_index = element_order.index(defined_order.first)
45
+ def validate_content!(element_order, model_class)
46
+ validate_sequence!(
47
+ extract_defined_order(model_class.attributes),
48
+ element_order,
49
+ )
50
+ end
62
51
 
63
- defined_order.each.with_index(start_index) do |element, i|
64
- unless element_order[i] == element
65
- raise Lutaml::Model::IncorrectSequenceError.new(element, element_order[i])
52
+ private
53
+
54
+ def validate_sequence!(defined_order, element_order)
55
+ eo_index = element_order_index(element_order, defined_order)
56
+
57
+ defined_order.each do |element, klass_attr|
58
+ if klass_attr.collection?
59
+ if add_missing_element?(element_order, eo_index, element, klass_attr)
60
+ element_order.insert(eo_index, element)
61
+ else
62
+ occurrences = klass_attr.sequenced_appearance_count(element_order, element, eo_index)
63
+ next eo_index += occurrences if occurrences.positive?
64
+ end
66
65
  end
66
+
67
+ next eo_index += 1 if element_order[eo_index] == element
68
+
69
+ raise Lutaml::Model::IncorrectSequenceError.new(element, element_order[eo_index])
70
+ end
71
+ end
72
+
73
+ def add_missing_element?(element_order, eo_index, element, klass_attr)
74
+ return false unless klass_attr.collection_range.min.zero?
75
+
76
+ element_order[eo_index] != element && !element_order.include?(element)
77
+ end
78
+
79
+ def element_order_index(element_order, defined_order)
80
+ element_order.find_index { |d| defined_order.key?(d) } || 0
81
+ end
82
+
83
+ def extract_defined_order(model_attrs)
84
+ @attributes.each_with_object({}) do |rule, acc|
85
+ acc[rule.name.to_s] = model_attrs[rule.to.to_s] ||
86
+ model_attrs[rule.to.to_sym]
67
87
  end
68
88
  end
89
+
90
+ def later_importable?(model)
91
+ model.is_a?(Symbol) ||
92
+ model.is_a?(String)
93
+ end
94
+
95
+ def import_mappings_later(model)
96
+ @model.sequence_importable_mappings[self] << model.to_sym
97
+ @model.set_mappings_imported(false)
98
+ end
69
99
  end
70
100
  end
71
101
  end
@@ -3,7 +3,6 @@ require_relative "config"
3
3
  require_relative "type"
4
4
  require_relative "attribute"
5
5
  require_relative "mapping_hash"
6
- # require_relative "mapping"
7
6
  require_relative "json_adapter"
8
7
  require_relative "comparable_model"
9
8
  require_relative "schema_location"
@@ -13,6 +12,7 @@ require_relative "choice"
13
12
  require_relative "sequence"
14
13
  require_relative "liquefiable"
15
14
  require_relative "transform"
15
+ require_relative "registrable"
16
16
 
17
17
  module Lutaml
18
18
  module Model
@@ -20,6 +20,7 @@ module Lutaml
20
20
  include ComparableModel
21
21
  include Validation
22
22
  include Lutaml::Model::Liquefiable
23
+ include Lutaml::Model::Registrable
23
24
 
24
25
  def self.included(base)
25
26
  base.extend(ClassMethods)
@@ -29,7 +30,7 @@ module Lutaml
29
30
  module ClassMethods
30
31
  include Lutaml::Model::Liquefiable::ClassMethods
31
32
 
32
- attr_accessor :attributes, :mappings, :choice_attributes
33
+ attr_accessor :choice_attributes, :mappings
33
34
 
34
35
  def inherited(subclass)
35
36
  super
@@ -48,6 +49,16 @@ module Lutaml
48
49
  instance_variable_set(:@model, self)
49
50
  end
50
51
 
52
+ def attributes(register = nil)
53
+ ensure_imports!(register) if finalized?
54
+ @attributes
55
+ end
56
+
57
+ def ensure_imports!(register = nil)
58
+ ensure_model_imports!(register)
59
+ ensure_choice_imports!(register)
60
+ end
61
+
51
62
  def model(klass = nil)
52
63
  if klass
53
64
  @model = klass
@@ -112,7 +123,7 @@ module Lutaml
112
123
  end
113
124
  define_method(:"#{name}=") do |value|
114
125
  value_set_for(name)
115
- instance_variable_set(:"@#{name}", attr.cast_value(value))
126
+ instance_variable_set(:"@#{name}", attr.cast_value(value, register))
116
127
  end
117
128
  end
118
129
  end
@@ -131,6 +142,25 @@ module Lutaml
131
142
  attr
132
143
  end
133
144
 
145
+ def restrict(name, options = {})
146
+ validate_attribute_options!(name, options)
147
+ attr = attributes[name]
148
+ attr.options.merge!(options)
149
+ attr.process_options!
150
+ name
151
+ end
152
+
153
+ def validate_attribute_options!(name, options)
154
+ invalid_opts = options.keys - Attribute::ALLOWED_OPTIONS
155
+ return if invalid_opts.empty?
156
+
157
+ raise Lutaml::Model::InvalidAttributeOptionsError.new(name, invalid_opts)
158
+ end
159
+
160
+ def register(name)
161
+ name&.to_sym
162
+ end
163
+
134
164
  def root?
135
165
  mappings_for(:xml)&.root?
136
166
  end
@@ -142,6 +172,14 @@ module Lutaml
142
172
  end
143
173
 
144
174
  def import_model_attributes(model)
175
+ if model.is_a?(Symbol) || model.is_a?(String)
176
+ importable_models[:import_model_attributes] << model.to_sym
177
+ @models_imported = false
178
+ @choices_imported = false
179
+ setup_trace_point
180
+ return
181
+ end
182
+
145
183
  model.attributes.each_value do |attr|
146
184
  define_attribute_methods(attr)
147
185
  end
@@ -151,8 +189,14 @@ module Lutaml
151
189
  end
152
190
 
153
191
  def import_model_mappings(model)
154
- import_model_with_root_error(model)
192
+ if model.is_a?(Symbol) || model.is_a?(String)
193
+ importable_models[:import_model_mappings] << model.to_sym
194
+ @models_imported = false
195
+ setup_trace_point
196
+ return
197
+ end
155
198
 
199
+ import_model_with_root_error(model)
156
200
  Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
157
201
  next unless model.mappings.key?(format)
158
202
 
@@ -178,11 +222,27 @@ module Lutaml
178
222
  end
179
223
 
180
224
  def import_model(model)
225
+ if model.is_a?(Symbol) || model.is_a?(String)
226
+ importable_models[:import_model] << model.to_sym
227
+ @models_imported = false
228
+ @choices_imported = false
229
+ setup_trace_point
230
+ return
231
+ end
232
+
181
233
  import_model_with_root_error(model)
182
234
  import_model_attributes(model)
183
235
  import_model_mappings(model)
184
236
  end
185
237
 
238
+ def importable_models
239
+ @importable_models ||= MappingHash.new { |h, k| h[k] = [] }
240
+ end
241
+
242
+ def importable_choices
243
+ @importable_choices ||= MappingHash.new { |h, k| h[k] = MappingHash.new { |h1, k1| h1[k1] = [] } }
244
+ end
245
+
186
246
  def add_enum_methods_to_model(klass, enum_name, values, collection: false)
187
247
  add_enum_getter_if_not_defined(klass, enum_name, collection)
188
248
  add_enum_setter_if_not_defined(klass, enum_name, values, collection)
@@ -264,15 +324,8 @@ module Lutaml
264
324
  end
265
325
 
266
326
  def process_mapping(format, &block)
267
- # klass = Lutaml::Model.const_get("#{format.to_s.capitalize}Mapping")
268
- # mappings[format] ||= klass.new
269
- # mappings[format].instance_eval(&block)
270
-
271
- # handle_root_assignment(mappings, format)
272
-
273
327
  klass = ::Lutaml::Model::Config.mappings_class_for(format)
274
328
  mappings[format] ||= klass.new
275
-
276
329
  mappings[format].instance_eval(&block)
277
330
 
278
331
  if mappings[format].respond_to?(:finalize)
@@ -281,24 +334,24 @@ module Lutaml
281
334
  end
282
335
 
283
336
  def from(format, data, options = {})
284
- return data if Utils.uninitialized?(data)
285
-
286
337
  adapter = Lutaml::Model::Config.adapter_for(format)
287
338
 
288
339
  doc = adapter.parse(data, options)
289
- public_send(:"of_#{format}", doc, options)
340
+ send("of_#{format}", doc, options)
290
341
  end
291
342
 
292
343
  def of(format, doc, options = {})
293
- if doc.is_a?(Array)
344
+ if doc.is_a?(Array) && format != :jsonl
294
345
  return doc.map { |item| send(:"of_#{format}", item) }
295
346
  end
296
347
 
297
348
  if format == :xml
298
- raise Lutaml::Model::NoRootMappingError.new(self) unless root?
349
+ valid = root? || options[:from_collection]
350
+ raise Lutaml::Model::NoRootMappingError.new(self) unless valid
299
351
 
300
352
  options[:encoding] = doc.encoding
301
353
  end
354
+ options[:register] = extract_register_id(options[:register])
302
355
 
303
356
  transformer = Lutaml::Model::Config.transformer_for(format)
304
357
  transformer.data_to_model(self, doc, format, options)
@@ -309,7 +362,6 @@ module Lutaml
309
362
  adapter = Lutaml::Model::Config.adapter_for(format)
310
363
 
311
364
  options[:mapper_class] = self if format == :xml
312
-
313
365
  adapter.new(value).public_send(:"to_#{format}", options)
314
366
  end
315
367
 
@@ -335,6 +387,7 @@ module Lutaml
335
387
  end
336
388
 
337
389
  def mappings_for(format)
390
+ @mappings[:xml]&.ensure_mappings_imported! if @mappings&.dig(:xml)&.finalized?
338
391
  mappings[format] || default_mappings(format)
339
392
  end
340
393
 
@@ -350,12 +403,21 @@ module Lutaml
350
403
  )
351
404
  end
352
405
 
353
- mapping.root(to_s.split("::").last) if format == :xml
406
+ mapping.root(Utils.base_class_name(self)) if format == :xml
354
407
  end
355
408
  end
356
409
 
357
410
  def apply_mappings(doc, format, options = {})
358
- instance = options[:instance] || model.new
411
+ register = options[:register] || Lutaml::Model::Config.default_register
412
+ instance = if options.key?(:instance)
413
+ options[:instance]
414
+ elsif model.include?(Lutaml::Model::Serialize)
415
+ model.new({}, register: register)
416
+ else
417
+ object = model.new
418
+ register_accessor_methods_for(object, register)
419
+ object
420
+ end
359
421
  return instance if Utils.blank?(doc)
360
422
 
361
423
  mappings = mappings_for(format)
@@ -364,7 +426,6 @@ module Lutaml
364
426
  return resolve_polymorphic(doc, format, mappings, instance, options)
365
427
  end
366
428
 
367
- # options[:mappings] = mappings.mappings
368
429
  transformer = Lutaml::Model::Config.transformer_for(format)
369
430
  transformer.data_to_model(self, doc, format, options)
370
431
  end
@@ -377,7 +438,7 @@ module Lutaml
377
438
  klass_name = polymorphic_mapping.polymorphic_map[klass_key]
378
439
  klass = Object.const_get(klass_name)
379
440
 
380
- klass.apply_mappings(doc, format, options)
441
+ klass.apply_mappings(doc, format, options.merge(register: instance.register))
381
442
  end
382
443
 
383
444
  def apply_value_map(value, value_map, attr)
@@ -400,7 +461,7 @@ module Lutaml
400
461
  end
401
462
 
402
463
  def empty_object(attr)
403
- return [] if attr.collection?
464
+ return attr.build_collection if attr.collection?
404
465
 
405
466
  ""
406
467
  end
@@ -422,6 +483,72 @@ module Lutaml
422
483
  value
423
484
  end
424
485
  end
486
+
487
+ def register_accessor_methods_for(object, register)
488
+ Utils.add_singleton_method_if_not_defined(object, :register) do
489
+ @register
490
+ end
491
+ Utils.add_singleton_method_if_not_defined(object, :register=) do |value|
492
+ @register = value
493
+ end
494
+ object.register = register
495
+ end
496
+
497
+ def extract_register_id(register)
498
+ if register
499
+ register.is_a?(Lutaml::Model::Register) ? register.id : register
500
+ elsif class_variable_defined?(:@@register)
501
+ class_variable_get(:@@register)
502
+ else
503
+ Lutaml::Model::Config.default_register
504
+ end
505
+ end
506
+
507
+ def ensure_model_imports!(register_id = nil)
508
+ return if @models_imported
509
+
510
+ register_id ||= Lutaml::Model::Config.default_register
511
+ register = Lutaml::Model::GlobalRegister.lookup(register_id)
512
+ importable_models.each do |method, models|
513
+ models.uniq.each do |model|
514
+ model_class = register.get_class_without_register(model)
515
+ import_model_with_root_error(model_class)
516
+
517
+ @model.public_send(method, model_class)
518
+ end
519
+ end
520
+
521
+ @models_imported = true
522
+ end
523
+
524
+ def ensure_choice_imports!(register_id = nil)
525
+ return if @choices_imported
526
+
527
+ register_id ||= Lutaml::Model::Config.default_register
528
+ register = Lutaml::Model::GlobalRegister.lookup(register_id)
529
+ importable_choices.each do |choice, choice_imports|
530
+ choice_imports.each do |method, models|
531
+ models.uniq!
532
+ choice.public_send(method, register.get_class_without_register(models.shift)) until models.empty?
533
+ end
534
+ end
535
+
536
+ @choices_imported = true
537
+ end
538
+
539
+ def setup_trace_point
540
+ @trace ||= TracePoint.new(:end) do |_tp|
541
+ if include?(Lutaml::Model::Serialize)
542
+ @finalized = true
543
+ @trace.disable
544
+ end
545
+ end
546
+ @trace.enable unless @trace.enabled?
547
+ end
548
+
549
+ def finalized?
550
+ @finalized
551
+ end
425
552
  end
426
553
 
427
554
  def self.register_format_mapping_method(format)
@@ -452,25 +579,27 @@ module Lutaml
452
579
  end
453
580
 
454
581
  define_method(:"to_#{format}") do |options = {}|
455
- raise Lutaml::Model::NoRootMappingError.new(self.class) if format == :xml && !self.class.root?
456
-
457
- options[:parse_encoding] = encoding if encoding
458
- self.class.to(format, self, options)
582
+ to_format(format, options)
459
583
  end
460
584
  end
461
585
 
462
- attr_accessor :element_order, :schema_location, :encoding
586
+ attr_accessor :element_order, :schema_location, :encoding, :register
463
587
  attr_writer :ordered, :mixed
464
588
 
465
589
  def initialize(attrs = {}, options = {})
466
590
  @using_default = {}
467
591
  return unless self.class.attributes
468
592
 
593
+ @register = extract_register_id(options[:register])
469
594
  set_ordering(attrs)
470
595
  set_schema_location(attrs)
471
596
  initialize_attributes(attrs, options)
472
597
  end
473
598
 
599
+ def extract_register_id(register)
600
+ self.class.extract_register_id(register)
601
+ end
602
+
474
603
  def value_map(options)
475
604
  {
476
605
  omitted: options[:omitted] || :nil,
@@ -479,30 +608,9 @@ module Lutaml
479
608
  }
480
609
  end
481
610
 
482
- def attr_value(attrs, name, attr_rule)
483
- value = if attrs.key?(name.to_sym)
484
- attrs[name.to_sym]
485
- elsif attrs.key?(name.to_s)
486
- attrs[name.to_s]
487
- else
488
- attr_rule.default
489
- end
490
-
491
- if attr_rule.collection? || value.is_a?(Array)
492
- value&.map do |v|
493
- if v.is_a?(Hash)
494
- attr_rule.type.new(v)
495
- else
496
- # TODO: This code is problematic because Type.cast does not know
497
- # about all the types.
498
- Lutaml::Model::Type.cast(v, attr_rule.type)
499
- end
500
- end
501
- else
502
- # TODO: This code is problematic because Type.cast does not know
503
- # about all the types.
504
- Lutaml::Model::Type.cast(value, attr_rule.type)
505
- end
611
+ def attr_value(attrs, name, attribute)
612
+ value = Utils.fetch_str_or_sym(attrs, name, attribute.default(register))
613
+ attribute.cast_value(value, register)
506
614
  end
507
615
 
508
616
  def using_default_for(attribute_name)
@@ -569,8 +677,22 @@ module Lutaml
569
677
  self.class.as_yaml(self)
570
678
  end
571
679
 
680
+ def to_format(format, options = {})
681
+ validate_root_mapping!(format, options)
682
+
683
+ options[:parse_encoding] = encoding if encoding
684
+ self.class.to(format, self, options)
685
+ end
686
+
572
687
  private
573
688
 
689
+ def validate_root_mapping!(format, options)
690
+ return if format != :xml
691
+ return if options[:collection] || self.class.root?
692
+
693
+ raise Lutaml::Model::NoRootMappingError.new(self.class)
694
+ end
695
+
574
696
  def set_ordering(attrs)
575
697
  return unless attrs.respond_to?(:ordered?)
576
698
 
@@ -600,9 +722,9 @@ module Lutaml
600
722
  def determine_value(attrs, name, attr)
601
723
  if attrs.key?(name) || attrs.key?(name.to_s)
602
724
  attr_value(attrs, name, attr)
603
- elsif attr.default_set?
725
+ elsif attr.default_set?(register)
604
726
  using_default_for(name)
605
- attr.default
727
+ attr.default(register)
606
728
  else
607
729
  Lutaml::Model::UninitializedClass.instance
608
730
  end
@@ -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