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
@@ -0,0 +1,19 @@
1
+ require_relative "../mapping/key_value_mapping"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Jsonl
6
+ class Mapping < Lutaml::Model::KeyValueMapping
7
+ def initialize
8
+ super(:jsonl)
9
+ end
10
+
11
+ def deep_dup
12
+ self.class.new.tap do |new_mapping|
13
+ new_mapping.instance_variable_set(:@mappings, duplicate_mappings)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "../mapping/key_value_mapping_rule"
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Jsonl
6
+ class MappingRule < Lutaml::Model::KeyValueMappingRule; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ require "json"
2
+ require_relative "document"
3
+
4
+ module Lutaml
5
+ module Model
6
+ module Jsonl
7
+ class StandardAdapter < Document
8
+ FORMAT_SYMBOL = :jsonl
9
+
10
+ def self.parse(jsonl, _options = {})
11
+ results = []
12
+ jsonl.split("\n").each do |line|
13
+ next if line.strip.empty?
14
+
15
+ begin
16
+ results << JSON.parse(line, create_additions: false)
17
+ rescue JSON::ParserError => e
18
+ warn "Skipping invalid line: #{e.message}"
19
+ end
20
+ end
21
+
22
+ results
23
+ end
24
+
25
+ def to_jsonl(*_args)
26
+ @jsons.map do |json|
27
+ JSON.generate(json)
28
+ end.join("\n")
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,19 @@
1
+ module Lutaml
2
+ module Model
3
+ module Jsonl
4
+ class Transform < Lutaml::Model::KeyValueTransform
5
+ def data_to_model(data, format, options = {})
6
+ mappings = defined_mappings_for(:jsonl) || mappings_for(:json)
7
+
8
+ super(data, format, options.merge(mappings: mappings))
9
+ end
10
+
11
+ def model_to_data(instance, format, options = {})
12
+ mappings = defined_mappings_for(:jsonl) || mappings_for(:json)
13
+
14
+ super(instance, format, options.merge(mappings: mappings))
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Jsonl
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative "jsonl/standard_adapter"
11
+ require_relative "jsonl/document"
12
+ require_relative "jsonl/mapping"
13
+ require_relative "jsonl/mapping_rule"
14
+ require_relative "jsonl/transform"
15
+
16
+ Lutaml::Model::FormatRegistry.register(
17
+ :jsonl,
18
+ mapping_class: Lutaml::Model::Jsonl::Mapping,
19
+ adapter_class: Lutaml::Model::Jsonl::StandardAdapter,
20
+ transformer: Lutaml::Model::Jsonl::Transform,
21
+ )
@@ -1,10 +1,11 @@
1
1
  module Lutaml
2
2
  module Model
3
3
  class KeyValueDocument
4
- attr_reader :attributes
4
+ attr_reader :attributes, :register
5
5
 
6
- def initialize(attributes = {})
6
+ def initialize(attributes = {}, register: nil)
7
7
  @attributes = attributes
8
+ @register = register || Lutaml::Model::Config.default_register
8
9
  end
9
10
 
10
11
  def [](key)
@@ -4,7 +4,7 @@ require_relative "key_value_mapping_rule"
4
4
  module Lutaml
5
5
  module Model
6
6
  class KeyValueMapping < Mapping
7
- attr_reader :mappings, :format
7
+ attr_reader :mappings, :format, :key_mappings, :value_mappings
8
8
 
9
9
  def initialize(format = nil)
10
10
  super()
@@ -12,6 +12,22 @@ module Lutaml
12
12
  @format = format
13
13
  end
14
14
 
15
+ def root(name = nil)
16
+ @root = name
17
+ end
18
+
19
+ def no_root
20
+ @root = nil
21
+ end
22
+
23
+ def no_root?
24
+ @root.nil?
25
+ end
26
+
27
+ def root_name
28
+ @root
29
+ end
30
+
15
31
  def map(
16
32
  name = nil,
17
33
  to: nil,
@@ -76,6 +92,39 @@ module Lutaml
76
92
 
77
93
  alias map_all_content map_all
78
94
 
95
+ def map_instances(to:)
96
+ map(root_name || to, to: to)
97
+ end
98
+
99
+ def map_key(to_instance: nil, as_attribute: nil)
100
+ @key_mappings = KeyValueMappingRule.new(
101
+ Constants::KEY_MAPPING_KEY,
102
+ to: nil,
103
+ to_instance: to_instance,
104
+ as_attribute: as_attribute,
105
+ )
106
+ end
107
+
108
+ def map_value(to_instance: nil, as_attribute: nil)
109
+ @value_mappings = KeyValueMappingRule.new(
110
+ Constants::VALUE_MAPPING_KEY,
111
+ to: nil,
112
+ to_instance: to_instance,
113
+ as_attribute: as_attribute,
114
+ )
115
+ end
116
+
117
+ def key_value_mappings
118
+ {
119
+ key: @key_mappings,
120
+ value: @value_mappings,
121
+ }.compact
122
+ end
123
+
124
+ def key_value_mappings?
125
+ Utils.present?(@key_mappings) || Utils.present?(@value_mappings)
126
+ end
127
+
79
128
  def name_for_mapping(root_mappings, name)
80
129
  return "root_mapping" if root_mappings
81
130
 
@@ -112,8 +161,10 @@ module Lutaml
112
161
  end
113
162
 
114
163
  def validate_root_mappings!(name)
115
- if @mappings.any?(&:root_mapping?) || (name == "root_mapping" && @mappings.any?)
116
- raise MultipleMappingsError.new("root_mappings cannot be used with other mappings")
164
+ if root_mapping || (name == "root_mapping" && @mappings.any?)
165
+ raise MultipleMappingsError.new(
166
+ "root_mappings cannot be used with other mappings",
167
+ )
117
168
  end
118
169
  end
119
170
 
@@ -126,7 +177,8 @@ module Lutaml
126
177
  end
127
178
 
128
179
  def validate_mappings!(_type)
129
- if (@raw_mapping && Utils.present?(@mappings)) || (!@raw_mapping && @mappings.any?(&:raw_mapping?))
180
+ if (@raw_mapping && Utils.present?(@mappings)) ||
181
+ (!@raw_mapping && @mappings.any?(&:raw_mapping?))
130
182
  raise StandardError, "map_all is not allowed with other mappings"
131
183
  end
132
184
  end
@@ -145,10 +197,18 @@ module Lutaml
145
197
  @mappings.find { |m| m.to.to_s == to.to_s }
146
198
  end
147
199
 
200
+ def find_by_name(name)
201
+ @mappings.find { |m| m.name.to_s == name.to_s }
202
+ end
203
+
148
204
  def polymorphic_mapping
149
205
  @mappings.find(&:polymorphic_mapping?)
150
206
  end
151
207
 
208
+ def root_mapping
209
+ @mappings.find(&:root_mapping?)
210
+ end
211
+
152
212
  Lutaml::Model::Config::KEY_VALUE_FORMATS.each do |format|
153
213
  define_method(:"format_#{format}?") do
154
214
  @format == format
@@ -9,6 +9,8 @@ module Lutaml
9
9
  def initialize(
10
10
  name,
11
11
  to:,
12
+ to_instance: nil,
13
+ as_attribute: nil,
12
14
  render_nil: false,
13
15
  render_default: false,
14
16
  render_empty: false,
@@ -27,6 +29,8 @@ module Lutaml
27
29
  super(
28
30
  name,
29
31
  to: to,
32
+ to_instance: to_instance,
33
+ as_attribute: as_attribute,
30
34
  render_nil: render_nil,
31
35
  render_default: render_default,
32
36
  render_empty: render_empty,
@@ -3,6 +3,8 @@ module Lutaml
3
3
  class MappingRule
4
4
  attr_reader :name,
5
5
  :to,
6
+ :to_instance,
7
+ :as_attribute,
6
8
  :render_nil,
7
9
  :render_default,
8
10
  :render_empty,
@@ -44,6 +46,8 @@ module Lutaml
44
46
  def initialize(
45
47
  name,
46
48
  to:,
49
+ to_instance: nil,
50
+ as_attribute: nil,
47
51
  render_nil: false,
48
52
  render_default: false,
49
53
  render_empty: false,
@@ -61,6 +65,8 @@ module Lutaml
61
65
  )
62
66
  @name = name
63
67
  @to = to
68
+ @to_instance = to_instance
69
+ @as_attribute = as_attribute
64
70
  @render_nil = render_nil
65
71
  @render_default = render_default
66
72
  @render_empty = render_empty
@@ -212,8 +218,7 @@ module Lutaml
212
218
  def deserialize(model, value, attributes, mapper_class = nil)
213
219
  handle_custom_method(model, value, mapper_class) ||
214
220
  handle_delegate(model, value, attributes) ||
215
- handle_transform_method(model, value, attributes) ||
216
- assign_value(model, value)
221
+ handle_transform_method(model, value, attributes)
217
222
  end
218
223
 
219
224
  def has_custom_method_for_serialization?
@@ -284,7 +289,7 @@ module Lutaml
284
289
  delegate_value = model.public_send(delegate)
285
290
  return if Utils.initialized?(delegate_value) && !delegate_value.nil?
286
291
 
287
- model.public_send(:"#{delegate}=", attributes[delegate].type.new)
292
+ model.public_send(:"#{delegate}=", attributes[delegate].type(model.register).new)
288
293
  end
289
294
 
290
295
  def handle_transform_method(model, value, attributes)
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ class Register
6
+ attr_reader :id, :models
7
+
8
+ def initialize(id)
9
+ @id = id
10
+ @models = {}
11
+ @global_substitutions = {}
12
+ @type_class_cache = {}
13
+ end
14
+
15
+ def register_model(klass, id: nil)
16
+ id ||= Utils.base_class_snake_case(klass).to_sym
17
+ return Lutaml::Model::Type.register(id, klass) if klass <= Lutaml::Model::Type::Value
18
+ raise NotRegistrableClassError.new(klass) unless klass.include?(Lutaml::Model::Registrable)
19
+
20
+ @models[id.to_sym] = klass
21
+ @models[klass.to_s] = klass
22
+ end
23
+
24
+ def resolve(klass_str)
25
+ @models[klass_str.to_s]
26
+ end
27
+
28
+ def get_class(klass_name)
29
+ expected_class = get_class_without_register(klass_name)
30
+ if !(expected_class < Lutaml::Model::Type::Value)
31
+ expected_class.class_variable_set(:@@register, id)
32
+ end
33
+ expected_class
34
+ end
35
+
36
+ def register_model_tree(klass)
37
+ register_model(klass)
38
+ if klass.include?(Lutaml::Model::Serialize)
39
+ register_attributes(klass.attributes)
40
+ end
41
+ end
42
+
43
+ def register_global_type_substitution(from_type:, to_type:)
44
+ @global_substitutions[from_type] = to_type
45
+ end
46
+
47
+ def register_attributes(attributes)
48
+ attributes.each_value do |attribute|
49
+ next unless attribute.unresolved_type.is_a?(Class)
50
+ next if built_in_type?(attribute.unresolved_type) || attribute.unresolved_type.nil?
51
+
52
+ register_model_tree(attribute.unresolved_type)
53
+ end
54
+ end
55
+
56
+ def substitutable?(klass)
57
+ @global_substitutions.key?(klass)
58
+ end
59
+
60
+ def substitute(klass)
61
+ @global_substitutions[klass]
62
+ end
63
+
64
+ def get_class_without_register(klass_name)
65
+ klass = extract_class_from(klass_name)
66
+ raise Lutaml::Model::UnknownTypeError.new(klass_name) unless klass
67
+
68
+ substitute(klass) || substitute(klass_name) || klass
69
+ end
70
+
71
+ private
72
+
73
+ def get_type_class(klass_name)
74
+ if klass_name.is_a?(String)
75
+ Lutaml::Model::Type.const_get(klass_name)
76
+ elsif klass_name.is_a?(Symbol)
77
+ Lutaml::Model::Type.lookup(klass_name)
78
+ end
79
+ end
80
+
81
+ def built_in_type?(type)
82
+ Lutaml::Model::Type::TYPE_CODES.value?(type.inspect) ||
83
+ Lutaml::Model::Type::TYPE_CODES.key?(type.to_s.to_sym)
84
+ end
85
+
86
+ def extract_class_from(klass)
87
+ return klass if klass.is_a?(Class)
88
+ return @models[klass] if @models.key?(klass)
89
+
90
+ # Check cache first
91
+ return @type_class_cache[klass] if @type_class_cache.key?(klass)
92
+
93
+ # If not in cache, try to get type class and cache it
94
+ if type_klass = get_type_class(klass)
95
+ @type_class_cache[klass] = type_klass
96
+ type_klass
97
+ end
98
+ end
99
+
100
+ def clear_type_class_cache
101
+ @type_class_cache.clear
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,6 @@
1
+ module Lutaml
2
+ module Model
3
+ module Registrable
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,64 @@
1
+ require_relative "generator/definitions_collection"
2
+ # require_relative "generator/collection"
3
+ require_relative "generator/ref"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Schema
8
+ class BaseSchema
9
+ class << self
10
+ def generate(klass, options = {})
11
+ schema = new(
12
+ klass,
13
+ schema: options[:schema],
14
+ id: options[:id],
15
+ title: options[:title],
16
+ description: options[:description],
17
+ ).generate_schema_hash
18
+
19
+ format_schema(schema, options)
20
+ end
21
+ end
22
+
23
+ attr_reader :schema, :klass
24
+ attr_accessor :id, :title, :description
25
+
26
+ def initialize(klass, schema:, id: nil, title: nil, description: nil)
27
+ @schema = schema
28
+ @klass = klass
29
+ @id = id
30
+ @title = title
31
+ @description = description
32
+ end
33
+
34
+ def generate_schema_hash
35
+ {
36
+ "$schema" => schema,
37
+ "$id" => id,
38
+ "description" => description,
39
+ "$ref" => "#/$defs/#{klass.name.gsub('::', '_')}",
40
+ "$defs" => generate_definitions(klass),
41
+ }.compact
42
+ end
43
+
44
+ private
45
+
46
+ def polymorphic?(attr)
47
+ Utils.present?(attr.options[:polymorphic])
48
+ end
49
+
50
+ def generate_definitions(klass)
51
+ Generator::DefinitionsCollection.from_class(klass).to_schema
52
+ end
53
+
54
+ def generate_polymorphic_definitions(attr)
55
+ return {} unless polymorphic?(attr)
56
+
57
+ attr.options[:polymorphic].each_with_object({}) do |child, result|
58
+ result.merge!(generate_definitions(child))
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module Decorators
7
+ class Attribute
8
+ ATTRIBUTE_TYPES = {
9
+ "string" => ":string",
10
+ "integer" => ":integer",
11
+ "boolean" => ":boolean",
12
+ "number" => ":float",
13
+ "object" => ":hash",
14
+ }.freeze
15
+
16
+ attr_reader :name, :options, :polymorphic
17
+ attr_accessor :type, :base_class
18
+
19
+ # @param attribute [Hash] The JSON schema attribute to be decorated.
20
+ def initialize(name, options)
21
+ @name = name
22
+ @options = options
23
+ @type = extract_type(options)
24
+ @polymorphic = options["oneOf"]&.map { |choice| choice["$ref"].split("/").last }
25
+ end
26
+
27
+ def default
28
+ @options["default"]
29
+ end
30
+
31
+ def polymorphic?
32
+ !!@polymorphic
33
+ end
34
+
35
+ def polymorphic_classes
36
+ return [] unless polymorphic?
37
+
38
+ @base_class.sub_classes.map do |klass|
39
+ klass.namespaced_name.gsub("_", "::")
40
+ end
41
+ end
42
+
43
+ def collection?
44
+ @options["type"] == "array"
45
+ end
46
+
47
+ def collection
48
+ return unless collection?
49
+
50
+ if @options["minItems"] && @options["maxItems"]
51
+ @options["minItems"]..@options["maxItems"]
52
+ elsif @options["minItems"]
53
+ @options["minItems"]..Float::INFINITY
54
+ elsif @options["maxItems"]
55
+ 0..@options["maxItems"]
56
+ else
57
+ true
58
+ end
59
+ end
60
+
61
+ def choice?
62
+ false
63
+ end
64
+
65
+ private
66
+
67
+ def lutaml_type_symbol(type)
68
+ return ":string" if type.nil?
69
+
70
+ type = type.first if type.is_a?(Array)
71
+
72
+ return array_type(options) if type == "array"
73
+ return object_type(options) if type == "object"
74
+
75
+ ATTRIBUTE_TYPES[type] || ":string"
76
+ end
77
+
78
+ def array_type(options)
79
+ lutaml_type_symbol(options["items"]["type"])
80
+ end
81
+
82
+ def extract_type(options)
83
+ return extract_type(options["items"]) if options["type"] == "array"
84
+
85
+ if options["type"]
86
+ lutaml_type_symbol(options["type"])
87
+ elsif options["$ref"]
88
+ options["$ref"].split("/").last.gsub("_", "::")
89
+ else
90
+ ":string" # Default to string if type is not recognized
91
+ end
92
+ end
93
+
94
+ def object_type(options)
95
+ if options["oneOf"]
96
+ {
97
+ "polymorphic" => options["oneOf"].map do |choice|
98
+ choice["$ref"].split("/").last.gsub("_", "::")
99
+ end,
100
+ }
101
+
102
+ elsif options["properties"]
103
+ options["properties"].map do |name, attr|
104
+ Attribute.new(name, attr)
105
+ end
106
+ else
107
+ ":hash" # Default to hash if no specific type is defined
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "attribute"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Schema
8
+ module Decorators
9
+ class Choices
10
+ attr_reader :attributes
11
+
12
+ # Decorates a collection of choice attributes.
13
+ # This class is used to handle attributes that are part of a choice
14
+ # constraint in a JSON schema. It provides a way to access the choice
15
+ # attributes in a structured manner.
16
+ def initialize(attributes)
17
+ @attributes = attributes.values
18
+ end
19
+
20
+ def choice?
21
+ true
22
+ end
23
+
24
+ def polymorphic?
25
+ false
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end