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,81 @@
1
+ require_relative "definition"
2
+ require_relative "../shared_methods"
3
+
4
+ module Lutaml
5
+ module Model
6
+ module Schema
7
+ module Generator
8
+ class DefinitionsCollection
9
+ class << self
10
+ include SharedMethods
11
+
12
+ def from_class(klass)
13
+ new.tap do |collection|
14
+ collection << Definition.new(klass)
15
+
16
+ process_attributes(collection, klass)
17
+ end
18
+ end
19
+
20
+ def process_attributes(collection, klass)
21
+ register = extract_register_from(klass)
22
+ klass.attributes.each_value do |attribute|
23
+ next unless attribute.serializable?(register)
24
+
25
+ process_attribute(collection, attribute, register)
26
+ end
27
+ end
28
+
29
+ def process_attribute(collection, attribute, register)
30
+ attr_type = Lutaml::Model::GlobalRegister.lookup(register).get_class(attribute.type)
31
+ collection.merge(DefinitionsCollection.from_class(attr_type))
32
+
33
+ process_polymorphic_types(collection, attribute)
34
+ end
35
+
36
+ def process_polymorphic_types(collection, attribute)
37
+ return unless attribute.options&.[](:polymorphic)
38
+
39
+ attribute.options[:polymorphic].each do |child|
40
+ collection.merge(DefinitionsCollection.from_class(child))
41
+ end
42
+ end
43
+ end
44
+
45
+ attr_reader :definitions
46
+
47
+ def initialize(definitions = [])
48
+ @definitions = definitions.map do |definition|
49
+ next definition if definition.is_a?(Definition)
50
+
51
+ Definition.new(definition)
52
+ end
53
+ end
54
+
55
+ def to_schema
56
+ definitions.each_with_object({}) do |definition, schema|
57
+ schema.merge!(definition.to_schema)
58
+ end
59
+ end
60
+
61
+ def add_definition(definition)
62
+ @definitions ||= []
63
+ @definitions << definition
64
+ end
65
+ alias << add_definition
66
+ alias push add_definition
67
+
68
+ def merge(collection)
69
+ @definitions ||= []
70
+
71
+ if collection.is_a?(Array)
72
+ @definitions.concat(collection)
73
+ else
74
+ @definitions.concat(collection.definitions)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,63 @@
1
+ require_relative "property"
2
+ require_relative "../shared_methods"
3
+
4
+ module Lutaml
5
+ module Model
6
+ module Schema
7
+ module Generator
8
+ class PropertiesCollection
9
+ class << self
10
+ include SharedMethods
11
+
12
+ def from_class(klass)
13
+ from_attributes(klass.attributes.values, extract_register_from(klass))
14
+ end
15
+
16
+ def from_attributes(attributes, register)
17
+ new(register: register).tap do |collection|
18
+ attributes.each do |attribute|
19
+ name = attribute.name
20
+ collection << Property.new(name, attribute, register: register)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ attr_reader :properties, :register
27
+
28
+ def initialize(properties = [], register:)
29
+ self.properties = properties
30
+ @register = register
31
+ end
32
+
33
+ def to_schema
34
+ properties.each_with_object({}) do |property, schema|
35
+ schema.merge!(property.to_schema)
36
+ end
37
+ end
38
+
39
+ def add_property(property)
40
+ @properties << if property.is_a?(Property)
41
+ property
42
+ else
43
+ Property.new(property.name, property, register: register)
44
+ end
45
+ end
46
+ alias << add_property
47
+ alias push add_property
48
+
49
+ def properties=(properties)
50
+ @properties ||= []
51
+ @properties.clear
52
+
53
+ @properties = properties.map do |property|
54
+ next property if property.is_a?(Property)
55
+
56
+ Property.new(property.name, property, register)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,110 @@
1
+ module Lutaml
2
+ module Model
3
+ module Schema
4
+ module Generator
5
+ # This class is used to generate a property schema definition.
6
+ # It is used in the context of generating JSON schemas.
7
+ class Property
8
+ attr_reader :name, :attribute, :register
9
+
10
+ def initialize(name, attribute, register:)
11
+ @name = name.to_s.gsub("::", "_")
12
+ @attribute = attribute
13
+ @register = register
14
+ end
15
+
16
+ def to_schema
17
+ { name => generate_attribute_schema(attribute) }
18
+ end
19
+
20
+ private
21
+
22
+ def generate_attribute_schema(attr, options = {})
23
+ include_null = options.fetch(:include_null, true)
24
+ inside_collection = options.fetch(:inside_collection, false)
25
+ if attr.collection? && !inside_collection
26
+ collection_schema(attr)
27
+ elsif attr.serializable?(register) && polymorphic?(attr)
28
+ polymorphic_schema(attr)
29
+ elsif attr.serializable?(register)
30
+ Generator::Ref.new(attr.type(register)).to_schema
31
+ else
32
+ primitive_schema(attr, include_null: include_null)
33
+ end
34
+ end
35
+
36
+ def collection_schema(attr)
37
+ schema = {
38
+ "type" => "array",
39
+ "items" => generate_attribute_schema(
40
+ attr,
41
+ include_null: false,
42
+ inside_collection: true,
43
+ ),
44
+ }
45
+
46
+ if attr.options[:collection].is_a?(Range)
47
+ add_collection_constraints!(schema, attr.options[:collection])
48
+ end
49
+
50
+ schema
51
+ end
52
+
53
+ def add_collection_constraints!(schema, range)
54
+ schema["minItems"] = range.begin
55
+ schema["maxItems"] = range.end if range.end
56
+ end
57
+
58
+ def polymorphic_schema(attr)
59
+ ref_schemas = attr.options[:polymorphic].map do |type|
60
+ Ref.new(type).to_schema
61
+ end
62
+
63
+ ref_schemas << Ref.new(attr.type).to_schema if attr.type
64
+
65
+ {
66
+ "type" => ["object", "null"],
67
+ "oneOf" => ref_schemas,
68
+ }
69
+ end
70
+
71
+ def primitive_schema(attr, include_null: true)
72
+ type = get_type(attr.type(register))
73
+ type = [type, "null"] if include_null
74
+
75
+ { "type" => type }.merge(get_constraints(attr))
76
+ end
77
+
78
+ def get_constraints(attr)
79
+ constraints = {}
80
+
81
+ # Add pattern validation
82
+ constraints["pattern"] = attr.pattern.source if attr.pattern
83
+
84
+ # Add default value
85
+ constraints["default"] = attr.default(register) if attr.default_set?(register)
86
+
87
+ # Add enumeration values
88
+ constraints["enum"] = attr.enum_values if attr.enum?
89
+
90
+ constraints
91
+ end
92
+
93
+ def polymorphic?(attr)
94
+ Utils.present?(attr.options[:polymorphic])
95
+ end
96
+
97
+ def get_type(type)
98
+ {
99
+ Lutaml::Model::Type::String => "string",
100
+ Lutaml::Model::Type::Integer => "integer",
101
+ Lutaml::Model::Type::Boolean => "boolean",
102
+ Lutaml::Model::Type::Float => "number",
103
+ Lutaml::Model::Type::Hash => "object",
104
+ }[type] || "string" # Default to string for unknown types
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,24 @@
1
+ module Lutaml
2
+ module Model
3
+ module Schema
4
+ module Generator
5
+ # This class is used to generate a reference to a schema definition.
6
+ # It is used in the context of generating JSON schemas.
7
+ class Ref
8
+ attr_reader :name
9
+
10
+ def initialize(type)
11
+ @type = type
12
+ @name = @type.name.gsub("::", "_")
13
+ end
14
+
15
+ def to_schema
16
+ {
17
+ "$ref" => "#/$defs/#{name}",
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ module Lutaml
2
+ module Model
3
+ module Schema
4
+ module Helpers
5
+ module TemplateHelper
6
+ def open_namespaces(namespaces)
7
+ namespaces.map { |ns| "module #{ns}" }.join("\n")
8
+ end
9
+
10
+ def close_namespaces(namespaces)
11
+ namespaces.reverse.map { "end" }.join("\n")
12
+ end
13
+
14
+ def indent(level)
15
+ " " * level
16
+ end
17
+
18
+ def attribute_line(attribute, level)
19
+ if attribute.choice?
20
+ <<~RUBY.chomp
21
+
22
+ #{indent(level)}choice do
23
+ #{attribute.attributes.map { |attr| attribute_line(attr, level + 1) }.join("\n")}
24
+ #{indent(level)}end
25
+ RUBY
26
+ else
27
+ "#{indent(level)}attribute #{attribute_properties(attribute)}"
28
+ end
29
+ end
30
+
31
+ def attribute_properties(attribute)
32
+ # properties = {}
33
+ # properties[:default] = attribute.default if attribute.default
34
+ # properties[:collection] = true if attribute.collection?
35
+
36
+ required_properties = ":#{attribute.name}, #{attribute.type}"
37
+ required_properties += ", default: #{attribute.default.inspect}" if attribute.default
38
+ required_properties += ", collection: #{attribute.collection}" if attribute.collection?
39
+ required_properties += ", values: #{attribute.options['enum']}" if attribute.options["enum"]
40
+ required_properties += ", pattern: /#{attribute.options['pattern']}/" if attribute.options["pattern"]
41
+ required_properties += ", polymorphic: [#{attribute.polymorphic_classes.join(', ')}]" if attribute.polymorphic?
42
+
43
+ required_properties
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,66 +1,59 @@
1
+ require_relative "shared_methods"
2
+ require_relative "base_schema"
3
+ require_relative "renderer"
4
+ require_relative "decorators/class_definition"
5
+ require_relative "decorators/definition_collection"
6
+
1
7
  require "json"
2
8
 
3
9
  module Lutaml
4
10
  module Model
5
11
  module Schema
6
- class JsonSchema
7
- def self.generate(klass, options = {})
8
- schema = {
9
- "$schema" => "https://json-schema.org/draft/2020-12/schema",
10
- "$id" => options[:id],
11
- "description" => options[:description],
12
- "$ref" => "#/$defs/#{klass.name}",
13
- "$defs" => generate_definitions(klass),
14
- }.compact
12
+ class JsonSchema < BaseSchema
13
+ class << self
14
+ include Lutaml::Model::Schema::SharedMethods
15
15
 
16
- options[:pretty] ? JSON.pretty_generate(schema) : schema.to_json
17
- end
16
+ def generate(
17
+ klass,
18
+ id: nil,
19
+ title: nil,
20
+ description: nil,
21
+ pretty: false
22
+ )
23
+ options = {
24
+ schema: "https://json-schema.org/draft/2020-12/schema",
25
+ id: id,
26
+ title: title,
27
+ description: description,
28
+ pretty: pretty,
29
+ }
18
30
 
19
- def self.generate_definitions(klass)
20
- defs = { klass.name => generate_class_schema(klass) }
21
- klass.attributes.each_value do |attr|
22
- if attr.type <= Lutaml::Model::Serialize
23
- defs.merge!(generate_definitions(attr.type))
24
- end
31
+ super(klass, options)
25
32
  end
26
- defs
27
- end
28
33
 
29
- def self.generate_class_schema(klass)
30
- {
31
- "type" => "object",
32
- "properties" => generate_properties(klass),
33
- "required" => klass.attributes.keys,
34
- }
35
- end
34
+ def format_schema(schema, options)
35
+ options[:pretty] ? JSON.pretty_generate(schema) : schema.to_json
36
+ end
37
+
38
+ def generate_model_classes(schema)
39
+ definitions = definition_collection_class.new(schema["$defs"])
36
40
 
37
- def self.generate_properties(klass)
38
- klass.attributes.transform_values do |attr|
39
- generate_property_schema(attr)
41
+ definitions.transform_values do |definition|
42
+ generate_model_class(definition)
43
+ end
40
44
  end
41
- end
42
45
 
43
- def self.generate_property_schema(attr)
44
- if attr.type <= Lutaml::Model::Serialize
45
- { "$ref" => "#/$defs/#{attr.type.name}" }
46
- elsif attr.collection?
47
- {
48
- "type" => "array",
49
- "items" => { "type" => get_json_type(attr.type) },
50
- }
51
- else
52
- { "type" => get_json_type(attr.type) }
46
+ private
47
+
48
+ def generate_model_class(schema)
49
+ template = File.join(__dir__, "templates", "model.erb")
50
+
51
+ Lutaml::Model::Schema::Renderer.render(template, schema: schema)
53
52
  end
54
- end
55
53
 
56
- def self.get_json_type(type)
57
- {
58
- Lutaml::Model::Type::String => "string",
59
- Lutaml::Model::Type::Integer => "integer",
60
- Lutaml::Model::Type::Boolean => "boolean",
61
- Lutaml::Model::Type::Float => "number",
62
- Lutaml::Model::Type::Hash => "object",
63
- }[type] || "string" # Default to string for unknown types
54
+ def definition_collection_class
55
+ Lutaml::Model::Schema::Decorators::DefinitionCollection
56
+ end
64
57
  end
65
58
  end
66
59
  end
@@ -4,11 +4,14 @@ module Lutaml
4
4
  module Model
5
5
  module Schema
6
6
  class RelaxngSchema
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.grammar(xmlns: "http://relaxng.org/ns/structure/1.0") do
10
13
  generate_start(xml, klass)
11
- generate_define(xml, klass)
14
+ generate_define(xml, klass, register)
12
15
  end
13
16
  end
14
17
 
@@ -21,34 +24,35 @@ module Lutaml
21
24
  end
22
25
  end
23
26
 
24
- def self.generate_attributes(xml, klass)
27
+ def self.generate_attributes(xml, klass, register)
25
28
  klass.attributes.each do |name, attr|
26
- if attr.type <= Lutaml::Model::Serialize
27
- xml.ref(name: attr.type.name)
29
+ attr_type = attr.type(register)
30
+ if attr_type <= Lutaml::Model::Serialize
31
+ xml.ref(name: attr_type.name)
28
32
  elsif attr.collection?
29
33
  xml.zeroOrMore do
30
34
  xml.element(name: name) do
31
- xml.data(type: get_relaxng_type(attr.type))
35
+ xml.data(type: get_relaxng_type(attr_type))
32
36
  end
33
37
  end
34
38
  else
35
39
  xml.element(name: name) do
36
- xml.data(type: get_relaxng_type(attr.type))
40
+ xml.data(type: get_relaxng_type(attr_type))
37
41
  end
38
42
  end
39
43
  end
40
44
  end
41
45
 
42
- def self.generate_define(xml, klass)
46
+ def self.generate_define(xml, klass, register)
43
47
  xml.define(name: klass.name) do
44
48
  xml.element(name: klass.name) do
45
- generate_attributes(xml, klass)
49
+ generate_attributes(xml, klass, register)
46
50
  end
47
51
  end
48
52
 
49
53
  klass.attributes.each_value do |attr|
50
- if attr.type <= Lutaml::Model::Serialize
51
- generate_define(xml, attr.type)
54
+ if attr.type(register) <= Lutaml::Model::Serialize
55
+ generate_define(xml, attr.type(register), register)
52
56
  end
53
57
  end
54
58
  end
@@ -0,0 +1,36 @@
1
+ require "erb"
2
+ require "ostruct"
3
+
4
+ require_relative "helpers/template_helper"
5
+
6
+ module Lutaml
7
+ module Model
8
+ module Schema
9
+ class Context
10
+ include Lutaml::Model::Schema::Helpers::TemplateHelper
11
+
12
+ attr_reader :schema
13
+
14
+ def initialize(schema)
15
+ @schema = schema
16
+ end
17
+ end
18
+
19
+ class Renderer
20
+ def self.render(template_path, context = {})
21
+ new(template_path).render(context)
22
+ end
23
+
24
+ def initialize(template_path)
25
+ @template = File.read(template_path)
26
+ end
27
+
28
+ def render(context = {})
29
+ context = Context.new(context[:schema])
30
+
31
+ ERB.new(@template, trim_mode: "-").result(context.instance_eval { binding })
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module SharedMethods
7
+ def extract_register_from(klass)
8
+ register = if klass.class_variable_defined?(:@@__register)
9
+ klass.class_variable_get(:@@__register)
10
+ end
11
+
12
+ case register
13
+ when Lutaml::Model::Register
14
+ register.id
15
+ when String, Symbol
16
+ register.to_sym
17
+ else
18
+ Lutaml::Model::Config.default_register
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ require "lutaml/model"
2
+
3
+ <%= open_namespaces(schema.namespaces) %>
4
+ <%= indent(schema.namespaces.size) %>class <%= schema.name %> < <%= schema.parent_class %>
5
+ <% schema.attributes.each do |attribute| -%>
6
+ <%= attribute_line(attribute, schema.namespaces.size + 1) %>
7
+ <% end -%>
8
+ <%= indent(schema.namespaces.size) %>end
9
+ <%= close_namespaces(schema.namespaces) %>
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class Attribute
8
+ attr_accessor :id,
9
+ :ref,
10
+ :name,
11
+ :type,
12
+ :default
13
+
14
+ TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
15
+ <%= indent %>attribute :<%= resolved_name %>, :<%= resolved_type %>
16
+ TEMPLATE
17
+
18
+ XML_MAPPING_TEMPLATE = ERB.new(<<~XML_MAPPING_TEMPLATE, trim_mode: "-")
19
+ <%= indent %>map_attribute :<%= resolved_name(change_case: false) %>, to: :<%= resolved_name %><%= namespace_prefix_option %>
20
+ XML_MAPPING_TEMPLATE
21
+
22
+ def initialize(name: nil, ref: nil)
23
+ @name = name
24
+ @ref = ref
25
+ end
26
+
27
+ def to_attributes(indent)
28
+ return if skippable?
29
+
30
+ TEMPLATE.result(binding)
31
+ end
32
+
33
+ def to_xml_mapping(indent)
34
+ return if skippable?
35
+
36
+ XML_MAPPING_TEMPLATE.result(binding)
37
+ end
38
+
39
+ def required_files
40
+ return if skippable?
41
+
42
+ raw_type = resolved_type(change_case: false)
43
+ if raw_type == "decimal"
44
+ "require \"bigdecimal\""
45
+ elsif !SimpleType.skippable?(raw_type)
46
+ "require_relative \"#{Utils.snake_case(raw_type)}\""
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def resolved_name(change_case: true)
53
+ @current_name ||= name || referenced_instance&.name
54
+ change_case ? Utils.snake_case(@current_name) : @current_name
55
+ end
56
+
57
+ def resolved_type(change_case: true)
58
+ @current_type ||= type || referenced_instance&.type
59
+ klass_name = last_of_split(@current_type)
60
+ change_case ? Utils.snake_case(klass_name) : klass_name
61
+ end
62
+
63
+ def referenced_instance
64
+ @referenced_instance ||= XmlCompiler.attributes[last_of_split]
65
+ end
66
+
67
+ def last_of_split(field = ref)
68
+ field&.split(":")&.last
69
+ end
70
+
71
+ def skippable?
72
+ resolved_name == "schema_location"
73
+ end
74
+
75
+ def namespace_prefix_option
76
+ return "" unless Utils.present?(ref)
77
+ return "" unless ref.start_with?("xml:")
78
+
79
+ ", namespace: \"http://www.w3.org/XML/1998/namespace\", prefix: \"xml\""
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end