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,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class Group
8
+ attr_accessor :name, :ref, :instance
9
+
10
+ GROUP_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
11
+ # frozen_string_literal: true
12
+
13
+ <%= "\n" + required_files.uniq.join("\n") -%>
14
+
15
+ class <%= Utils.camel_case(base_name) %> < Lutaml::Model::Serializable
16
+ <%= definitions_content %>
17
+ <%= xml_mapping_block -%>
18
+
19
+ <%= @indent %>def self.register
20
+ <%= extended_indent %>@register ||= Lutaml::Model::GlobalRegister.lookup(Lutaml::Model::Config.default_register)
21
+ <%= @indent %>end
22
+
23
+ <%= @indent %>def self.register_class_with_id
24
+ <%= extended_indent %>register.register_model(self, id: :<%= Utils.snake_case(base_name) %>)
25
+ <%= @indent %>end
26
+ end
27
+
28
+ <%= Utils.camel_case(base_name) %>.register_class_with_id
29
+ TEMPLATE
30
+
31
+ XML_MAPPING_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
32
+ <%= @indent %>xml do
33
+ <%= extended_indent %>no_root
34
+ <%= instance&.to_xml_mapping(extended_indent) -%>
35
+ <%= @indent %>end
36
+ TEMPLATE
37
+
38
+ def initialize(name = nil, ref = nil)
39
+ @name = name
40
+ @ref = ref
41
+ end
42
+
43
+ def to_xml_mapping(indent = @indent)
44
+ if Utils.present?(@ref)
45
+ "#{indent}import_model_mappings :#{Utils.snake_case(base_name)}\n"
46
+ else
47
+ @instance.to_xml_mapping(indent * 2)
48
+ end
49
+ end
50
+
51
+ def to_class(options: {})
52
+ setup_options(options)
53
+ GROUP_TEMPLATE.result(binding)
54
+ end
55
+
56
+ def required_files
57
+ if Utils.blank?(name) && Utils.present?(ref)
58
+ "require_relative \"#{Utils.snake_case(ref.split(':').last)}\""
59
+ else
60
+ @instance&.required_files
61
+ end
62
+ end
63
+
64
+ def to_attributes(indent = @indent)
65
+ if Utils.present?(@ref)
66
+ "#{indent}import_model_attributes :#{Utils.snake_case(base_name)}\n"
67
+ else
68
+ @instance.to_attributes(indent)
69
+ end
70
+ end
71
+
72
+ def base_name
73
+ (name || ref)&.split(":")&.last
74
+ end
75
+
76
+ private
77
+
78
+ def setup_options(options)
79
+ @indent = " " * options&.fetch(:indent, 2)
80
+ end
81
+
82
+ def definitions_content
83
+ @definitions_content ||= instance.to_attributes(@indent)
84
+ end
85
+
86
+ def extended_indent
87
+ @indent * 2
88
+ end
89
+
90
+ def xml_mapping_block
91
+ XML_MAPPING_TEMPLATE.result(binding)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class Restriction
8
+ # transform is not a 'xsd' type, but it is used for internal purposes.
9
+ attr_accessor :min_inclusive,
10
+ :max_inclusive,
11
+ :min_exclusive,
12
+ :max_exclusive,
13
+ :enumerations,
14
+ :max_length,
15
+ :min_length,
16
+ :base_class,
17
+ :pattern,
18
+ :length,
19
+ :transform
20
+
21
+ MIN_MAX_BOUNDS = ERB.new(<<~TEMPLATE, trim_mode: "-")
22
+ <%= "\#{indent}options[:max] = \#{max_inclusive || max_exclusive}" if max_bound_exist? %>
23
+ <%= "\#{indent}options[:min] = \#{min_inclusive || min_exclusive}" if min_bound_exist? %>
24
+ TEMPLATE
25
+
26
+ PATTERN = ERB.new(<<~TEMPLATE, trim_mode: "-")
27
+ <%= "\#{indent}options[:pattern] = %r{\#{pattern}}" %>
28
+ TEMPLATE
29
+
30
+ ENUMERATIONS = ERB.new(<<~TEMPLATE, trim_mode: "-")
31
+ <%= "\#{indent}options[:values] = [\#{casted_enumerations}]" %>
32
+ TEMPLATE
33
+
34
+ TRANSFORM = ERB.new(<<~TEMPLATE, trim_mode: "-")
35
+ <%= "\#{indent}value = \#{transform}" %>
36
+ TEMPLATE
37
+
38
+ def to_method_body(indent = nil)
39
+ [
40
+ value_for(ENUMERATIONS, type: :enumerations, indent: indent),
41
+ value_for(MIN_MAX_BOUNDS, type: :min_max_bounds, indent: indent),
42
+ value_for(PATTERN, type: :pattern, indent: indent),
43
+ value_for(TRANSFORM, type: :transform, indent: indent),
44
+ ].compact.join
45
+ end
46
+
47
+ def required_files
48
+ if base_class_name == :decimal
49
+ "require \"bigdecimal\""
50
+ elsif !SimpleType::SUPPORTED_DATA_TYPES.dig(base_class_name, :skippable)
51
+ "require_relative \"#{Utils.snake_case(base_class_name)}\""
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def value_for(constant, type:, indent:)
58
+ return unless send("#{type}_exist?")
59
+
60
+ indent ||= " "
61
+ constant.result(binding)
62
+ end
63
+
64
+ def min_max_bounds_exist?
65
+ min_bound_exist? || max_bound_exist?
66
+ end
67
+
68
+ def min_bound_exist?
69
+ Utils.present?(min_inclusive) || Utils.present?(min_exclusive)
70
+ end
71
+
72
+ def max_bound_exist?
73
+ Utils.present?(max_inclusive) || Utils.present?(max_exclusive)
74
+ end
75
+
76
+ def pattern_exist?
77
+ Utils.present?(pattern)
78
+ end
79
+
80
+ def enumerations_exist?
81
+ Utils.present?(enumerations)
82
+ end
83
+
84
+ def transform_exist?
85
+ Utils.present?(transform)
86
+ end
87
+
88
+ def base_class_name
89
+ return if Utils.blank?(base_class)
90
+
91
+ base_class.split(":").last.to_sym
92
+ end
93
+
94
+ def casted_enumerations
95
+ enumerations.map { |enumeration| "super(#{enumeration.inspect})" }.join(", ")
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class Sequence
8
+ attr_accessor :instances
9
+
10
+ XML_MAPPING_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
11
+ <%= indent %>sequence do
12
+ <%= content -%>
13
+ <%= indent %>end
14
+ TEMPLATE
15
+
16
+ def initialize
17
+ @instances = []
18
+ end
19
+
20
+ def <<(instance)
21
+ return if instance.nil?
22
+
23
+ @instances << instance
24
+ end
25
+
26
+ def to_attributes(indent)
27
+ instances.filter_map { |instance| instance.to_attributes(indent) }.join
28
+ end
29
+
30
+ def to_xml_mapping(indent)
31
+ content = xml_block_content(indent)
32
+ return "" if content.empty?
33
+
34
+ XML_MAPPING_TEMPLATE.result(binding)
35
+ end
36
+
37
+ def required_files
38
+ @instances.map(&:required_files)
39
+ end
40
+
41
+ private
42
+
43
+ def xml_block_content(indent)
44
+ instances.filter_map { |instance| instance.to_xml_mapping(indent * 2) }.join
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class SimpleContent
8
+ attr_accessor :instances, :base_class
9
+
10
+ def initialize
11
+ @instances = []
12
+ @base_class = nil
13
+ end
14
+
15
+ def <<(instance)
16
+ return if instance.nil?
17
+
18
+ @instances << instance
19
+ end
20
+
21
+ def to_attributes(indent = nil)
22
+ instances.filter_map { |instance| instance.to_attributes(indent) }.join("\n")
23
+ end
24
+
25
+ def to_xml_mapping(indent = nil)
26
+ instances.filter_map { |instance| instance.to_xml_mapping(indent) }.join("\n")
27
+ end
28
+
29
+ def required_files
30
+ instances.map(&:required_files).flatten.compact.uniq
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module Schema
6
+ module XmlCompiler
7
+ class SimpleType
8
+ attr_accessor :class_name, :base_class, :instance, :unions
9
+
10
+ LUTAML_VALUE_CLASS_NAME = "Lutaml::Model::Type::Value"
11
+
12
+ SUPPORTED_DATA_TYPES = {
13
+ nonNegativeInteger: { skippable: false, class_name: "Lutaml::Model::Type::String", validations: { pattern: /\+?[0-9]+/ } },
14
+ normalizedString: { skippable: false, class_name: "Lutaml::Model::Type::String", validations: { transform: "value.gsub(/[\\r\\n\\t]/, ' ')" } },
15
+ positiveInteger: { skippable: false, class_name: "Lutaml::Model::Type::Integer", validations: { min_inclusive: 0 } },
16
+ unsignedShort: { skippable: false, class_name: "Lutaml::Model::Type::Integer", validations: { min_inclusive: 0, max_inclusive: 65535 } },
17
+ base64Binary: { skippable: false, class_name: "Lutaml::Model::Type::String", validations: { pattern: /\A([A-Za-z0-9+\/]+={0,2}|\s)*\z/ } },
18
+ unsignedLong: { skippable: false, class_name: "Lutaml::Model::Type::Integer", validations: { min_inclusive: 0, max_inclusive: 18446744073709551615 } },
19
+ unsignedByte: { skippable: false, class_name: "Lutaml::Model::Type::Integer", validations: { min_inclusive: 0, max_inclusive: 255 } },
20
+ unsignedInt: { skippable: false, class_name: "Lutaml::Model::Type::Integer", validations: { min_inclusive: 0, max_inclusive: 4294967295 } },
21
+ hexBinary: { skippable: false, class_name: "Lutaml::Model::Type::String", validations: { pattern: /([0-9a-fA-F]{2})*/ } },
22
+ language: { skippable: false, class_name: "Lutaml::Model::Type::String", validations: { pattern: /\A[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*\z/ } },
23
+ dateTime: { skippable: true, class_name: "Lutaml::Model::Type::DateTime" },
24
+ boolean: { skippable: true, class_name: "Lutaml::Model::Type::Boolean" },
25
+ integer: { skippable: true, class_name: "Lutaml::Model::Type::Integer" },
26
+ decimal: { skippable: true, class_name: "Lutaml::Model::Type::Decimal" },
27
+ string: { skippable: true, class_name: "Lutaml::Model::Type::String" },
28
+ double: { skippable: true, class_name: "Lutaml::Model::Type::Float" },
29
+ NCName: { skippable: false, class_name: "Lutaml::Model::Type::String", validations: { pattern: /\A[a-zA-Z_][\w.-]*\z/ } },
30
+ anyURI: { skippable: false, class_name: "Lutaml::Model::Type::String", validations: { pattern: "\\A\#{URI::DEFAULT_PARSER.make_regexp(%w[http https ftp])}\\z" } },
31
+ token: { skippable: false, class_name: "Lutaml::Model::Type::String", validations: { pattern: /\A[^\t\n\f\r ]+(?: [^\t\n\f\r ]+)*\z/ } },
32
+ byte: { skippable: false, class_name: "Lutaml::Model::Type::Integer", validations: { min_inclusive: -128, max_inclusive: 127 } },
33
+ long: { skippable: false, class_name: "Lutaml::Model::Type::Decimal" },
34
+ int: { skippable: true, class_name: "Lutaml::Model::Type::Integer" },
35
+ id: { skippable: false, class_name: "Lutaml::Model::Type::String", validations: { pattern: /\A[a-zA-Z_][\w.-]*\z/ } },
36
+ }.freeze
37
+
38
+ INSTANCE_MODEL_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
39
+ # frozen_string_literal: true
40
+ require "lutaml/model"
41
+
42
+ <%= "\#{required_files}\n" -%>
43
+ class <%= klass_name %><%= " < \#{parent_class}" if parent_class %>
44
+ <%= @indent %>def self.cast(value, options = {})
45
+ <%= extended_indent %>return if value.nil?
46
+
47
+ <%= instance&.to_method_body(extended_indent) %>
48
+ value = super(value, options)
49
+ value
50
+ end
51
+
52
+ <%= @indent %>def self.register
53
+ <%= extended_indent %>@register ||= Lutaml::Model::GlobalRegister.lookup(Lutaml::Model::Config.default_register)
54
+ <%= @indent %>end
55
+
56
+ <%= @indent %>def self.register_class_with_id
57
+ <%= extended_indent %>register.register_model(self, id: :<%= Utils.snake_case(class_name) %>)
58
+ <%= @indent %>end
59
+ end
60
+
61
+ <%= klass_name %>.register_class_with_id
62
+ TEMPLATE
63
+
64
+ UNION_MODEL_TEMPLATE = ERB.new(<<~TEMPLATE, trim_mode: "-")
65
+ # frozen_string_literal: true
66
+ require "lutaml/model"
67
+ <%= union_required_files %>
68
+
69
+ class <%= klass_name %> < <%= LUTAML_VALUE_CLASS_NAME %>
70
+ <%= @indent %>def self.cast(value, options = {})
71
+ <%= extended_indent %>return if value.nil?
72
+
73
+ <%= union_class_method_body %>
74
+ end
75
+
76
+ <%= @indent %>def self.register
77
+ <%= extended_indent %>@register ||= Lutaml::Model::GlobalRegister.lookup(Lutaml::Model::Config.default_register)
78
+ <%= @indent %>end
79
+
80
+ <%= @indent %>def self.register_class_with_id
81
+ <%= extended_indent %>register.register_model(self, id: :<%= Utils.snake_case(class_name) %>)
82
+ <%= @indent %>end
83
+ end
84
+
85
+ <%= klass_name %>.register_class_with_id
86
+ TEMPLATE
87
+
88
+ def initialize(name, unions = [])
89
+ @class_name = name
90
+ @unions = unions
91
+ end
92
+
93
+ def to_class(options: {})
94
+ setup_options(options)
95
+ template = unions&.any? ? UNION_MODEL_TEMPLATE : INSTANCE_MODEL_TEMPLATE
96
+ template.result(binding)
97
+ end
98
+
99
+ def required_files
100
+ files = Array(instance&.required_files)
101
+ files << "require_relative \"#{Utils.snake_case(parent_class)}\"" if require_parent?
102
+ files.join("\n")
103
+ end
104
+
105
+ private
106
+
107
+ def setup_options(options)
108
+ @indent = " " * options&.fetch(:indent, 2)
109
+ end
110
+
111
+ def klass_name
112
+ @klass_name ||= Utils.camel_case(class_name)
113
+ end
114
+
115
+ def require_parent?
116
+ return false if Utils.blank?(base_class)
117
+
118
+ !!!SUPPORTED_DATA_TYPES[base_class&.to_sym]&.dig(:skippable)
119
+ end
120
+
121
+ def parent_class
122
+ type_info = SUPPORTED_DATA_TYPES[base_class&.to_sym]
123
+ return type_info[:class_name] if type_info&.dig(:skippable)
124
+ return Utils.camel_case(base_class.to_s) if !!!type_info&.dig(:skippable) && Utils.present?(base_class)
125
+
126
+ LUTAML_VALUE_CLASS_NAME
127
+ end
128
+
129
+ def union_class_method_body
130
+ unions.map do |union|
131
+ "#{extended_indent}register.get_class(:#{down_union_class_name(union)}).cast(value, options)"
132
+ end.join(" ||\n ")
133
+ end
134
+
135
+ def union_required_files
136
+ unions.filter_map do |union|
137
+ next if SUPPORTED_DATA_TYPES.dig(last_of_split(union).to_sym, :skippable)
138
+
139
+ "require_relative \"#{down_union_class_name(union)}\""
140
+ end.join("\n")
141
+ end
142
+
143
+ def down_union_class_name(union)
144
+ Utils.snake_case(last_of_split(union))
145
+ end
146
+
147
+ def last_of_split(field)
148
+ field&.split(":")&.last
149
+ end
150
+
151
+ def extended_indent
152
+ (@indent || " ") * 2
153
+ end
154
+
155
+ class << self
156
+ def setup_supported_types
157
+ SUPPORTED_DATA_TYPES
158
+ .reject { |_, simple_type| simple_type[:skippable] }
159
+ .each_with_object({}) do |(name, simple_type), hash|
160
+ str_name = name.to_s
161
+ new(str_name).tap do |instance|
162
+ instance.base_class = Utils.base_class_snake_case(simple_type[:class_name])
163
+ instance.instance = setup_restriction(instance.base_class, simple_type[:validations])
164
+ hash[str_name] = instance
165
+ end
166
+ end
167
+ end
168
+
169
+ def setup_restriction(base_class, validations)
170
+ return unless validations
171
+
172
+ Restriction.new.tap do |restriction|
173
+ restriction.base_class = base_class
174
+ restriction.min_inclusive = validations[:min_inclusive]
175
+ restriction.max_inclusive = validations[:max_inclusive]
176
+ restriction.pattern = validations[:pattern]
177
+ restriction.transform = validations[:transform]
178
+ end
179
+ end
180
+
181
+ def skippable?(type)
182
+ SUPPORTED_DATA_TYPES.dig(type&.to_sym, :skippable)
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end