lutaml-model 0.7.1 → 0.7.7

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 (259) 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.yml +1 -1
  10. data/.rubocop_todo.yml +58 -84
  11. data/Gemfile +4 -1
  12. data/README.adoc +3115 -365
  13. data/RELEASE_NOTES.adoc +346 -0
  14. data/docs/custom_adapters.adoc +144 -0
  15. data/docs/custom_registers.adoc +228 -0
  16. data/docs/schema_generation.adoc +898 -0
  17. data/docs/schema_import.adoc +364 -0
  18. data/flake.lock +114 -0
  19. data/flake.nix +103 -0
  20. data/lib/lutaml/model/attribute.rb +242 -100
  21. data/lib/lutaml/model/choice.rb +30 -0
  22. data/lib/lutaml/model/collection.rb +212 -0
  23. data/lib/lutaml/model/comparable_model.rb +3 -3
  24. data/lib/lutaml/model/config.rb +71 -42
  25. data/lib/lutaml/model/constants.rb +2 -0
  26. data/lib/lutaml/model/error/element_count_out_of_range_error.rb +29 -0
  27. data/lib/lutaml/model/error/invalid_attribute_name_error.rb +15 -0
  28. data/lib/lutaml/model/error/invalid_attribute_options_error.rb +16 -0
  29. data/lib/lutaml/model/error/invalid_choice_range_error.rb +3 -5
  30. data/lib/lutaml/model/error/polymorphic_error.rb +7 -2
  31. data/lib/lutaml/model/error/register/not_registrable_class_error.rb +11 -0
  32. data/lib/lutaml/model/error/type/invalid_value_error.rb +5 -3
  33. data/lib/lutaml/model/error/type/max_bound_error.rb +20 -0
  34. data/lib/lutaml/model/error/type/max_length_error.rb +20 -0
  35. data/lib/lutaml/model/error/type/min_bound_error.rb +20 -0
  36. data/lib/lutaml/model/error/type/min_length_error.rb +20 -0
  37. data/lib/lutaml/model/error/type/pattern_not_matched_error.rb +18 -0
  38. data/lib/lutaml/model/error/validation_failed_error.rb +9 -0
  39. data/lib/lutaml/model/error.rb +10 -0
  40. data/lib/lutaml/model/errors.rb +36 -0
  41. data/lib/lutaml/model/format_registry.rb +44 -0
  42. data/lib/lutaml/model/global_register.rb +41 -0
  43. data/lib/lutaml/model/hash_adapter/document.rb +11 -0
  44. data/lib/lutaml/model/hash_adapter/mapping.rb +19 -0
  45. data/lib/lutaml/model/hash_adapter/mapping_rule.rb +9 -0
  46. data/lib/lutaml/model/hash_adapter/standard_adapter.rb +17 -0
  47. data/lib/lutaml/model/hash_adapter/transform.rb +8 -0
  48. data/lib/lutaml/model/hash_adapter.rb +21 -0
  49. data/lib/lutaml/model/json/document.rb +11 -0
  50. data/lib/lutaml/model/json/mapping.rb +19 -0
  51. data/lib/lutaml/model/json/mapping_rule.rb +9 -0
  52. data/lib/lutaml/model/{json_adapter → json}/multi_json_adapter.rb +4 -5
  53. data/lib/lutaml/model/{json_adapter/standard_json_adapter.rb → json/standard_adapter.rb} +5 -3
  54. data/lib/lutaml/model/json/transform.rb +8 -0
  55. data/lib/lutaml/model/json.rb +21 -0
  56. data/lib/lutaml/model/jsonl/document.rb +14 -0
  57. data/lib/lutaml/model/jsonl/mapping.rb +19 -0
  58. data/lib/lutaml/model/jsonl/mapping_rule.rb +9 -0
  59. data/lib/lutaml/model/jsonl/standard_adapter.rb +33 -0
  60. data/lib/lutaml/model/jsonl/transform.rb +19 -0
  61. data/lib/lutaml/model/jsonl.rb +21 -0
  62. data/lib/lutaml/model/key_value_document.rb +28 -0
  63. data/lib/lutaml/model/mapping/key_value_mapping.rb +85 -13
  64. data/lib/lutaml/model/mapping/key_value_mapping_rule.rb +5 -0
  65. data/lib/lutaml/model/mapping/mapping.rb +13 -0
  66. data/lib/lutaml/model/mapping/mapping_rule.rb +15 -9
  67. data/lib/lutaml/model/register.rb +105 -0
  68. data/lib/lutaml/model/{toml_adapter.rb → registrable.rb} +1 -1
  69. data/lib/lutaml/model/schema/base_schema.rb +64 -0
  70. data/lib/lutaml/model/schema/decorators/attribute.rb +114 -0
  71. data/lib/lutaml/model/schema/decorators/choices.rb +31 -0
  72. data/lib/lutaml/model/schema/decorators/class_definition.rb +85 -0
  73. data/lib/lutaml/model/schema/decorators/definition_collection.rb +97 -0
  74. data/lib/lutaml/model/schema/generator/definition.rb +53 -0
  75. data/lib/lutaml/model/schema/generator/definitions_collection.rb +81 -0
  76. data/lib/lutaml/model/schema/generator/properties_collection.rb +63 -0
  77. data/lib/lutaml/model/schema/generator/property.rb +110 -0
  78. data/lib/lutaml/model/schema/generator/ref.rb +24 -0
  79. data/lib/lutaml/model/schema/helpers/template_helper.rb +49 -0
  80. data/lib/lutaml/model/schema/json_schema.rb +42 -49
  81. data/lib/lutaml/model/schema/relaxng_schema.rb +14 -10
  82. data/lib/lutaml/model/schema/renderer.rb +36 -0
  83. data/lib/lutaml/model/schema/shared_methods.rb +24 -0
  84. data/lib/lutaml/model/schema/templates/model.erb +9 -0
  85. data/lib/lutaml/model/schema/xml_compiler/attribute.rb +85 -0
  86. data/lib/lutaml/model/schema/xml_compiler/attribute_group.rb +45 -0
  87. data/lib/lutaml/model/schema/xml_compiler/choice.rb +65 -0
  88. data/lib/lutaml/model/schema/xml_compiler/complex_content.rb +27 -0
  89. data/lib/lutaml/model/schema/xml_compiler/complex_content_restriction.rb +34 -0
  90. data/lib/lutaml/model/schema/xml_compiler/complex_type.rb +136 -0
  91. data/lib/lutaml/model/schema/xml_compiler/element.rb +104 -0
  92. data/lib/lutaml/model/schema/xml_compiler/group.rb +97 -0
  93. data/lib/lutaml/model/schema/xml_compiler/restriction.rb +101 -0
  94. data/lib/lutaml/model/schema/xml_compiler/sequence.rb +50 -0
  95. data/lib/lutaml/model/schema/xml_compiler/simple_content.rb +36 -0
  96. data/lib/lutaml/model/schema/xml_compiler/simple_type.rb +189 -0
  97. data/lib/lutaml/model/schema/xml_compiler.rb +231 -587
  98. data/lib/lutaml/model/schema/xsd_schema.rb +12 -8
  99. data/lib/lutaml/model/schema/yaml_schema.rb +41 -35
  100. data/lib/lutaml/model/schema.rb +1 -0
  101. data/lib/lutaml/model/sequence.rb +60 -30
  102. data/lib/lutaml/model/serialization_adapter.rb +22 -0
  103. data/lib/lutaml/model/serialize.rb +296 -548
  104. data/lib/lutaml/model/services/base.rb +11 -0
  105. data/lib/lutaml/model/services/logger.rb +54 -0
  106. data/lib/lutaml/model/services/rule_value_extractor.rb +92 -0
  107. data/lib/lutaml/model/services/transformer.rb +48 -0
  108. data/lib/lutaml/model/services/type/validator/number.rb +25 -0
  109. data/lib/lutaml/model/services/type/validator/string.rb +52 -0
  110. data/lib/lutaml/model/services/type/validator.rb +43 -0
  111. data/lib/lutaml/model/services/validator.rb +145 -0
  112. data/lib/lutaml/model/services.rb +5 -0
  113. data/lib/lutaml/model/toml/document.rb +11 -0
  114. data/lib/lutaml/model/toml/mapping.rb +27 -0
  115. data/lib/lutaml/model/toml/mapping_rule.rb +9 -0
  116. data/lib/lutaml/model/{toml_adapter → toml}/toml_rb_adapter.rb +3 -3
  117. data/lib/lutaml/model/toml/tomlib_adapter.rb +19 -0
  118. data/lib/lutaml/model/toml/transform.rb +8 -0
  119. data/lib/lutaml/model/toml.rb +30 -0
  120. data/lib/lutaml/model/transform/key_value_transform.rb +297 -0
  121. data/lib/lutaml/model/transform/xml_transform.rb +228 -0
  122. data/lib/lutaml/model/transform.rb +93 -0
  123. data/lib/lutaml/model/type/boolean.rb +1 -1
  124. data/lib/lutaml/model/type/date.rb +1 -1
  125. data/lib/lutaml/model/type/date_time.rb +1 -1
  126. data/lib/lutaml/model/type/decimal.rb +11 -9
  127. data/lib/lutaml/model/type/float.rb +2 -1
  128. data/lib/lutaml/model/type/integer.rb +24 -21
  129. data/lib/lutaml/model/type/string.rb +4 -2
  130. data/lib/lutaml/model/type/time.rb +1 -1
  131. data/lib/lutaml/model/type/time_without_date.rb +1 -1
  132. data/lib/lutaml/model/type/value.rb +11 -10
  133. data/lib/lutaml/model/type.rb +5 -2
  134. data/lib/lutaml/model/uninitialized_class.rb +1 -1
  135. data/lib/lutaml/model/utils.rb +56 -4
  136. data/lib/lutaml/model/validation.rb +6 -4
  137. data/lib/lutaml/model/version.rb +1 -1
  138. data/lib/lutaml/model/{xml_adapter → xml}/builder/nokogiri.rb +2 -2
  139. data/lib/lutaml/model/{xml_adapter → xml}/builder/oga.rb +10 -10
  140. data/lib/lutaml/model/{xml_adapter → xml}/builder/ox.rb +1 -1
  141. data/lib/lutaml/model/{xml_adapter/xml_document.rb → xml/document.rb} +42 -25
  142. data/lib/lutaml/model/xml/element.rb +32 -0
  143. data/lib/lutaml/model/xml/mapping.rb +471 -0
  144. data/lib/lutaml/model/xml/mapping_rule.rb +149 -0
  145. data/lib/lutaml/model/xml/nokogiri_adapter.rb +234 -0
  146. data/lib/lutaml/model/{xml_adapter → xml}/oga/document.rb +1 -1
  147. data/lib/lutaml/model/{xml_adapter → xml}/oga/element.rb +7 -2
  148. data/lib/lutaml/model/xml/oga_adapter.rb +172 -0
  149. data/lib/lutaml/model/xml/ox_adapter.rb +229 -0
  150. data/lib/lutaml/model/xml/transform.rb +8 -0
  151. data/lib/lutaml/model/{xml_adapter → xml}/xml_attribute.rb +1 -1
  152. data/lib/lutaml/model/{xml_adapter → xml}/xml_element.rb +9 -31
  153. data/lib/lutaml/model/{xml_adapter → xml}/xml_namespace.rb +1 -1
  154. data/lib/lutaml/model/xml.rb +31 -0
  155. data/lib/lutaml/model/xml_adapter/element.rb +11 -25
  156. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +6 -223
  157. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +13 -163
  158. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +10 -207
  159. data/lib/lutaml/model/yaml/document.rb +10 -0
  160. data/lib/lutaml/model/yaml/mapping.rb +19 -0
  161. data/lib/lutaml/model/yaml/mapping_rule.rb +9 -0
  162. data/lib/lutaml/model/{yaml_adapter/standard_yaml_adapter.rb → yaml/standard_adapter.rb} +4 -3
  163. data/lib/lutaml/model/yaml/transform.rb +8 -0
  164. data/lib/lutaml/model/yaml.rb +21 -0
  165. data/lib/lutaml/model/yamls/document.rb +14 -0
  166. data/lib/lutaml/model/yamls/mapping.rb +19 -0
  167. data/lib/lutaml/model/yamls/mapping_rule.rb +9 -0
  168. data/lib/lutaml/model/yamls/standard_adapter.rb +34 -0
  169. data/lib/lutaml/model/yamls/transform.rb +19 -0
  170. data/lib/lutaml/model/yamls.rb +21 -0
  171. data/lib/lutaml/model.rb +15 -4
  172. data/lutaml-model.gemspec +0 -4
  173. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +5 -6
  174. data/spec/fixtures/xml/advanced_test_schema.xsd +134 -0
  175. data/spec/fixtures/xml/examples/nested_categories.xml +55 -0
  176. data/spec/fixtures/xml/examples/valid_catalog.xml +43 -0
  177. data/spec/fixtures/xml/product_catalog.xsd +151 -0
  178. data/spec/fixtures/xml/specifications_schema.xsd +38 -0
  179. data/spec/lutaml/model/attribute_collection_spec.rb +101 -0
  180. data/spec/lutaml/model/attribute_spec.rb +41 -44
  181. data/spec/lutaml/model/cdata_spec.rb +7 -7
  182. data/spec/lutaml/model/choice_spec.rb +44 -0
  183. data/spec/lutaml/model/custom_bibtex_adapter_spec.rb +598 -0
  184. data/spec/lutaml/model/custom_collection_spec.rb +830 -0
  185. data/spec/lutaml/model/custom_vobject_adapter_spec.rb +1226 -0
  186. data/spec/lutaml/model/global_register_spec.rb +108 -0
  187. data/spec/lutaml/model/group_spec.rb +27 -10
  188. data/spec/lutaml/model/hash/adapter_spec.rb +255 -0
  189. data/spec/lutaml/model/json_adapter_spec.rb +6 -6
  190. data/spec/lutaml/model/jsonl/standard_adapter_spec.rb +91 -0
  191. data/spec/lutaml/model/jsonl_spec.rb +229 -0
  192. data/spec/lutaml/model/key_value_mapping_spec.rb +25 -1
  193. data/spec/lutaml/model/mixed_content_spec.rb +24 -24
  194. data/spec/lutaml/model/multiple_mapping_spec.rb +6 -6
  195. data/spec/lutaml/model/ordered_content_spec.rb +6 -6
  196. data/spec/lutaml/model/polymorphic_spec.rb +178 -0
  197. data/spec/lutaml/model/register/key_value_spec.rb +275 -0
  198. data/spec/lutaml/model/register/xml_spec.rb +185 -0
  199. data/spec/lutaml/model/register_spec.rb +147 -0
  200. data/spec/lutaml/model/root_mappings_spec.rb +3 -3
  201. data/spec/lutaml/model/rule_value_extractor_spec.rb +162 -0
  202. data/spec/lutaml/model/schema/generator/definitions_collection_spec.rb +120 -0
  203. data/spec/lutaml/model/schema/json_schema_spec.rb +412 -51
  204. data/spec/lutaml/model/schema/json_schema_to_models_spec.rb +383 -0
  205. data/spec/lutaml/model/schema/xml_compiler/attribute_group_spec.rb +65 -0
  206. data/spec/lutaml/model/schema/xml_compiler/attribute_spec.rb +63 -0
  207. data/spec/lutaml/model/schema/xml_compiler/choice_spec.rb +71 -0
  208. data/spec/lutaml/model/schema/xml_compiler/complex_content_restriction_spec.rb +55 -0
  209. data/spec/lutaml/model/schema/xml_compiler/complex_content_spec.rb +37 -0
  210. data/spec/lutaml/model/schema/xml_compiler/complex_type_spec.rb +173 -0
  211. data/spec/lutaml/model/schema/xml_compiler/element_spec.rb +63 -0
  212. data/spec/lutaml/model/schema/xml_compiler/group_spec.rb +86 -0
  213. data/spec/lutaml/model/schema/xml_compiler/restriction_spec.rb +76 -0
  214. data/spec/lutaml/model/schema/xml_compiler/sequence_spec.rb +59 -0
  215. data/spec/lutaml/model/schema/xml_compiler/simple_content_spec.rb +55 -0
  216. data/spec/lutaml/model/schema/xml_compiler/simple_type_spec.rb +181 -0
  217. data/spec/lutaml/model/schema/xml_compiler_spec.rb +503 -1804
  218. data/spec/lutaml/model/schema/yaml_schema_spec.rb +249 -26
  219. data/spec/lutaml/model/sequence_spec.rb +36 -0
  220. data/spec/lutaml/model/serializable_spec.rb +201 -94
  221. data/spec/lutaml/model/toml_adapter_spec.rb +6 -6
  222. data/spec/lutaml/model/toml_spec.rb +51 -0
  223. data/spec/lutaml/model/transformation_spec.rb +72 -15
  224. data/spec/lutaml/model/type_spec.rb +8 -4
  225. data/spec/lutaml/model/uninitialized_class_spec.rb +96 -0
  226. data/spec/lutaml/model/utils_spec.rb +3 -3
  227. data/spec/lutaml/model/xml/derived_attributes_spec.rb +1 -1
  228. data/spec/lutaml/model/xml/namespace_spec.rb +57 -0
  229. data/spec/lutaml/model/xml/root_mappings/nested_child_mappings_spec.rb +217 -0
  230. data/spec/lutaml/model/xml/xml_element_spec.rb +7 -1
  231. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +2 -2
  232. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +2 -2
  233. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +2 -2
  234. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +12 -12
  235. data/spec/lutaml/model/xml_adapter_spec.rb +30 -6
  236. data/spec/lutaml/model/xml_mapping_rule_spec.rb +14 -7
  237. data/spec/lutaml/model/xml_mapping_spec.rb +27 -15
  238. data/spec/lutaml/model/xml_spec.rb +63 -0
  239. data/spec/lutaml/model/yaml_adapter_spec.rb +3 -5
  240. data/spec/lutaml/model/yamls/standard_adapter_spec.rb +183 -0
  241. data/spec/lutaml/model/yamls_spec.rb +294 -0
  242. data/spec/spec_helper.rb +4 -3
  243. metadata +162 -60
  244. data/lib/lutaml/model/json_adapter/json_document.rb +0 -20
  245. data/lib/lutaml/model/json_adapter/json_object.rb +0 -28
  246. data/lib/lutaml/model/loggable.rb +0 -15
  247. data/lib/lutaml/model/mapping/json_mapping.rb +0 -17
  248. data/lib/lutaml/model/mapping/toml_mapping.rb +0 -25
  249. data/lib/lutaml/model/mapping/xml_mapping.rb +0 -389
  250. data/lib/lutaml/model/mapping/xml_mapping_rule.rb +0 -139
  251. data/lib/lutaml/model/mapping/yaml_mapping.rb +0 -17
  252. data/lib/lutaml/model/mapping.rb +0 -14
  253. data/lib/lutaml/model/schema/templates/simple_type.rb +0 -247
  254. data/lib/lutaml/model/toml_adapter/toml_document.rb +0 -20
  255. data/lib/lutaml/model/toml_adapter/toml_object.rb +0 -28
  256. data/lib/lutaml/model/toml_adapter/tomlib_adapter.rb +0 -20
  257. data/lib/lutaml/model/yaml_adapter/yaml_document.rb +0 -20
  258. data/lib/lutaml/model/yaml_adapter/yaml_object.rb +0 -28
  259. data/lib/lutaml/model/yaml_adapter.rb +0 -8
@@ -0,0 +1,13 @@
1
+ module Lutaml
2
+ module Model
3
+ class Mapping
4
+ def initialize
5
+ @mappings = []
6
+ end
7
+
8
+ def mappings
9
+ raise NotImplementedError, "#{self.class.name} must implement `mappings`."
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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,12 +218,15 @@ 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
- def using_custom_methods?
220
- !custom_methods.empty?
224
+ def has_custom_method_for_serialization?
225
+ !custom_methods.empty? && custom_methods[:to]
226
+ end
227
+
228
+ def has_custom_method_for_deserialization?
229
+ !custom_methods.empty? && custom_methods[:from]
221
230
  end
222
231
 
223
232
  def multiple_mappings?
@@ -280,14 +289,11 @@ module Lutaml
280
289
  delegate_value = model.public_send(delegate)
281
290
  return if Utils.initialized?(delegate_value) && !delegate_value.nil?
282
291
 
283
- model.public_send(:"#{delegate}=", attributes[delegate].type.new)
292
+ model.public_send(:"#{delegate}=", attributes[delegate].type(model.__register).new)
284
293
  end
285
294
 
286
295
  def handle_transform_method(model, value, attributes)
287
- transform_method = transform[:import] || attributes[to].transform_import_method
288
- return unless transform_method
289
-
290
- assign_value(model, transform_method.call(value))
296
+ assign_value(model, ImportTransformer.call(value, self, attributes[to]))
291
297
  true
292
298
  end
293
299
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  module Lutaml
2
2
  module Model
3
- module TomlAdapter
3
+ module Registrable
4
4
  end
5
5
  end
6
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
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "attribute"
4
+ require_relative "choices"
5
+
6
+ module Lutaml
7
+ module Model
8
+ module Schema
9
+ module Decorators
10
+ class ClassDefinition
11
+ attr_accessor :base_class, :sub_classes
12
+ attr_reader :additional_properties, :properties, :namespaced_name
13
+
14
+ # Decorates a JSON schema information to be used in class definitions.
15
+ # This class is used to provide a structured way to handle schema
16
+ # information for class definitions, including attributes,
17
+ # required fields, and JSON mappings.
18
+ #
19
+ # @param schema [Hash] The JSON schema to be decorated.
20
+ # @param options [Hash] Additional options for the decorator.
21
+ def initialize(namespaced_name, schema)
22
+ @namespaced_name = namespaced_name
23
+ @choices = schema["oneOf"] || []
24
+ @additional_properties = schema["additionalProperties"] || false
25
+ @polymorphic_attributes = []
26
+
27
+ @properties = (schema["properties"] || {}).to_h do |name, attr|
28
+ attribute = Decorators::Attribute.new(name, attr)
29
+ polymorphic_attributes << attribute if attribute.polymorphic?
30
+
31
+ [name, attribute]
32
+ end
33
+
34
+ @base_class = nil
35
+ @sub_classes = []
36
+ end
37
+
38
+ def name
39
+ @name ||= @namespaced_name.split("_").last
40
+ end
41
+
42
+ def namespaces
43
+ @namespaces ||= @namespaced_name.split("_")[0..-2]
44
+ end
45
+
46
+ def parent_class
47
+ @parent_class ||= @base_class&.namespaced_name&.gsub("_", "::") || "Lutaml::Model::Serializable"
48
+ end
49
+
50
+ def choice?
51
+ @choices&.any?
52
+ end
53
+
54
+ def attributes
55
+ return @properties.values if !choice?
56
+
57
+ choice_attributes = {}
58
+ @choices.each do |choice|
59
+ choice["properties"].each do |name, attr|
60
+ choice_attributes[name] = properties[name] || Decorators::Attribute.new(name, attr)
61
+ properties[name] = nil if properties[name]
62
+ end
63
+ end
64
+
65
+ @properties.values.compact + [Choices.new(choice_attributes)]
66
+ end
67
+
68
+ def polymorphic?
69
+ polymorphic_attributes.any?
70
+ end
71
+
72
+ def polymorphic_attributes
73
+ return @polymorphic_attributes if !@polymorphic_attributes.nil?
74
+
75
+ attributes.each do |attr|
76
+ @polymorphic_attributes << attr if attr.polymorphic?
77
+ end
78
+
79
+ @polymorphic_attributes
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "class_definition"
4
+
5
+ module Lutaml
6
+ module Model
7
+ module Schema
8
+ module Decorators
9
+ class DefinitionCollection
10
+ include Enumerable
11
+
12
+ def initialize(definitions_hash)
13
+ @definition_hash = definitions_hash
14
+ @polymorphic_classes = []
15
+
16
+ @definitions = definitions_hash.to_h do |name, schema|
17
+ definition = definition_class.new(name, schema)
18
+ @polymorphic_classes << definition if definition.polymorphic?
19
+
20
+ [name, definition]
21
+ end
22
+
23
+ resolve_polymorphic_base_types!
24
+ end
25
+
26
+ def each(&block)
27
+ @definitions.each(&block)
28
+ end
29
+
30
+ def transform_values(&block)
31
+ @definitions.transform_values(&block)
32
+ end
33
+
34
+ def [](name)
35
+ @definitions[name] || raise("Definition not found: #{name}")
36
+ end
37
+
38
+ private
39
+
40
+ def definition_class
41
+ Lutaml::Model::Schema::Decorators::ClassDefinition
42
+ end
43
+
44
+ def resolve_polymorphic_base_types!
45
+ @polymorphic_classes.each do |poly_class|
46
+ poly_class.polymorphic_attributes.each do |attr|
47
+ resolve_polymorphic_attribute(attr)
48
+ end
49
+ end
50
+ end
51
+
52
+ def resolve_polymorphic_attribute(attr)
53
+ child_classes = resolve_child_classes(attr.polymorphic)
54
+ return if child_classes.size < 2
55
+
56
+ base_class = find_common_base_class(child_classes)
57
+ return unless base_class
58
+
59
+ update_polymorphic_attribute(attr, base_class)
60
+ refactor_subclasses(child_classes, base_class)
61
+ end
62
+
63
+ def resolve_child_classes(class_names)
64
+ class_names.filter_map { |name| @definitions[name] }
65
+ end
66
+
67
+ def update_polymorphic_attribute(attr, base_class)
68
+ attr.base_class = base_class
69
+ attr.type = base_class.namespaced_name.gsub("_", "::")
70
+ end
71
+
72
+ def refactor_subclasses(child_classes, base_class)
73
+ common_keys = base_class.properties.keys.to_set
74
+ (child_classes - [base_class]).each do |child|
75
+ child.base_class = base_class
76
+ child.properties.reject! { |key, _| common_keys.include?(key) }
77
+ base_class.sub_classes << child unless base_class.sub_classes.include?(child)
78
+ end
79
+ end
80
+
81
+ def find_common_base_class(class_defs)
82
+ return nil if class_defs.size < 2
83
+
84
+ # Get intersection of all property names
85
+ common_props = class_defs.map { |klass| klass.properties.keys }.reduce(:&)
86
+ return nil if common_props.empty?
87
+
88
+ # Look for a class that exactly matches the common properties — assume it's the base
89
+ class_defs.find do |klass|
90
+ klass.properties.keys.to_set == common_props.to_set
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,53 @@
1
+ require_relative "properties_collection"
2
+ require_relative "../shared_methods"
3
+
4
+ module Lutaml
5
+ module Model
6
+ module Schema
7
+ module Generator
8
+ class Definition
9
+ include SharedMethods
10
+
11
+ attr_reader :type, :name
12
+
13
+ def initialize(type)
14
+ @type = type
15
+ @name = type.name.gsub("::", "_")
16
+ end
17
+
18
+ def to_schema
19
+ @schema = {
20
+ name => {
21
+ "type" => "object",
22
+ "additionalProperties" => false,
23
+ "properties" => properties_to_schema(type),
24
+ },
25
+ }
26
+
27
+ # Add choice validation if present
28
+ if type.choice_attributes.any?
29
+ @schema[name]["oneOf"] = generate_choice_attributes(type)
30
+ end
31
+
32
+ @schema
33
+ end
34
+
35
+ private
36
+
37
+ def generate_choice_attributes(type)
38
+ type.choice_attributes.map do |choice|
39
+ {
40
+ "type" => "object",
41
+ "properties" => PropertiesCollection.from_attributes(choice.attributes, extract_register_from(type)).to_schema,
42
+ }
43
+ end
44
+ end
45
+
46
+ def properties_to_schema(type)
47
+ PropertiesCollection.from_class(type).to_schema
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end