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,162 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Lutaml::Model::RuleValueExtractor do
4
+ subject(:extractor) do
5
+ described_class.new(rule, doc, format, attr, register, options)
6
+ end
7
+
8
+ let(:rule) { instance_double(Lutaml::Model::Jsonl::MappingRule) }
9
+ let(:doc) { { "name" => "Test", "value" => 123 } }
10
+ let(:format) { :json }
11
+ let(:attr) { instance_double(Lutaml::Model::Attribute) }
12
+ let(:register) { instance_double(Lutaml::Model::Register) }
13
+ let(:options) { {} }
14
+
15
+ describe "#call" do
16
+ context "when rule has single mapping" do
17
+ before do
18
+ allow(rule).to receive_messages(
19
+ multiple_mappings?: false,
20
+ name: "name",
21
+ root_mapping?: false,
22
+ raw_mapping?: false,
23
+ )
24
+ end
25
+
26
+ it "returns value for the rule name" do
27
+ expect(extractor.call).to eq("Test")
28
+ end
29
+ end
30
+
31
+ context "when rule has multiple mappings" do
32
+ before do
33
+ allow(rule).to receive_messages(
34
+ multiple_mappings?: true,
35
+ name: ["name", "value"],
36
+ root_mapping?: false,
37
+ raw_mapping?: false,
38
+ )
39
+ end
40
+
41
+ it "returns first initialized value" do
42
+ expect(extractor.call).to eq("Test")
43
+ end
44
+ end
45
+
46
+ context "when rule is root mapping" do
47
+ before do
48
+ allow(rule).to receive_messages(
49
+ multiple_mappings?: false,
50
+ name: "name",
51
+ root_mapping?: true,
52
+ )
53
+ end
54
+
55
+ it "returns the entire document" do
56
+ expect(extractor.call).to eq(doc)
57
+ end
58
+ end
59
+
60
+ context "when rule is raw mapping" do
61
+ before do
62
+ allow(rule).to receive_messages(
63
+ multiple_mappings?: false,
64
+ name: "name",
65
+ root_mapping?: false,
66
+ raw_mapping?: true,
67
+ )
68
+ end
69
+
70
+ it "converts document to specified format" do
71
+ expect(extractor.call).to eq('{"name":"Test","value":123}')
72
+ end
73
+ end
74
+
75
+ context "when value needs transformation" do
76
+ let(:options) do
77
+ {
78
+ key_mappings: instance_double(Lutaml::Model::KeyValueMappingRule, to_instance: :key),
79
+ value_mappings: instance_double(Lutaml::Model::KeyValueMappingRule, as_attribute: :value),
80
+ }
81
+ end
82
+
83
+ before do
84
+ allow(rule).to receive_messages(
85
+ multiple_mappings?: false,
86
+ name: "data",
87
+ root_mapping?: false,
88
+ raw_mapping?: false,
89
+ )
90
+ end
91
+
92
+ it "transforms hash values" do
93
+ doc = { "data" => { "test" => { "nested" => "value" } } }
94
+ extractor = described_class.new(rule, doc, format, attr, register, options)
95
+
96
+ expected = [{
97
+ "key" => "test",
98
+ "nested" => "value",
99
+ }]
100
+ expect(extractor.call).to eq(expected)
101
+ end
102
+
103
+ it "transforms simple values" do
104
+ doc = { "data" => { "test" => "value" } }
105
+ extractor = described_class.new(rule, doc, format, attr, register, options)
106
+
107
+ expected = [{
108
+ "key" => "test",
109
+ "value" => "value",
110
+ }]
111
+ expect(extractor.call).to eq(expected)
112
+ end
113
+ end
114
+
115
+ context "when value is not found" do
116
+ before do
117
+ allow(rule).to receive_messages(
118
+ multiple_mappings?: false,
119
+ name: "nonexistent",
120
+ root_mapping?: false,
121
+ raw_mapping?: false,
122
+ )
123
+ allow(attr).to receive(:default_set?).with(register).and_return(false)
124
+ end
125
+
126
+ it "returns uninitialized value" do
127
+ expect(extractor.call).to be_a(Lutaml::Model::UninitializedClass)
128
+ end
129
+ end
130
+
131
+ context "when attribute has default value" do
132
+ before do
133
+ allow(rule).to receive_messages(
134
+ multiple_mappings?: false,
135
+ name: "nonexistent",
136
+ root_mapping?: false,
137
+ raw_mapping?: false,
138
+ )
139
+ allow(attr).to receive(:default_set?).with(register).and_return(true)
140
+ allow(attr).to receive(:default).with(register).and_return("default_value")
141
+ end
142
+
143
+ it "returns default value" do
144
+ expect(extractor.call).to eq("default_value")
145
+ end
146
+ end
147
+ end
148
+
149
+ describe "#uninitialized_value" do
150
+ it "returns an instance of UninitializedClass" do
151
+ expect(
152
+ extractor.send(:uninitialized_value),
153
+ ).to be_a(Lutaml::Model::UninitializedClass)
154
+ end
155
+
156
+ it "returns the same instance for multiple calls" do
157
+ first_call = extractor.send(:uninitialized_value)
158
+ second_call = extractor.send(:uninitialized_value)
159
+ expect(first_call).to equal(second_call)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require "lutaml/model/schema"
5
+
6
+ module DefinitionsCollectionSpec
7
+ class Address < Lutaml::Model::Serializable
8
+ attribute :street, Lutaml::Model::Type::String
9
+ end
10
+
11
+ class HomeAddress < Address
12
+ attribute :is_home, Lutaml::Model::Type::Boolean
13
+ end
14
+
15
+ class WorkAddress < Address
16
+ attribute :company, Lutaml::Model::Type::String
17
+ end
18
+
19
+ class Person < Lutaml::Model::Serializable
20
+ attribute :name, Lutaml::Model::Type::String
21
+ attribute :age, Lutaml::Model::Type::Integer
22
+ attribute :address, Address, polymorphic: [HomeAddress, WorkAddress]
23
+ end
24
+ end
25
+
26
+ RSpec.describe Lutaml::Model::Schema::Generator::DefinitionsCollection do
27
+ let(:klass) { DefinitionsCollectionSpec::Person }
28
+ let(:register) { :def_collection }
29
+
30
+ before do
31
+ collection_register = Lutaml::Model::Register.new(register)
32
+ Lutaml::Model::GlobalRegister.register(collection_register)
33
+ end
34
+
35
+ describe ".from_class" do
36
+ subject(:collection) { described_class.from_class(klass) }
37
+
38
+ let(:all_classes) do
39
+ [
40
+ klass,
41
+ DefinitionsCollectionSpec::Address,
42
+ DefinitionsCollectionSpec::HomeAddress,
43
+ DefinitionsCollectionSpec::WorkAddress,
44
+ ]
45
+ end
46
+
47
+ it "creates a new DefinitionsCollection" do
48
+ expect(collection).to be_a(described_class)
49
+ end
50
+
51
+ it "generated definition" do
52
+ definition_class = Lutaml::Model::Schema::Generator::Definition
53
+ expect(collection.definitions.first).to be_a(definition_class)
54
+ end
55
+
56
+ it "initializes with the main class definition" do
57
+ expect(collection.definitions.size).to eq(all_classes.size)
58
+ end
59
+
60
+ it "includes definitions for main class and and all custom classes" do
61
+ definition_types = collection.definitions.map(&:type)
62
+ expect(definition_types).to include(*all_classes)
63
+ end
64
+ end
65
+
66
+ describe "#to_schema" do
67
+ subject(:schema) { collection.to_schema }
68
+
69
+ let(:collection) { described_class.from_class(klass) }
70
+
71
+ it "returns a hash" do
72
+ expect(schema).to be_a(Hash)
73
+ end
74
+
75
+ it "includes schema for all definitions" do
76
+ expect(schema.keys.size).to eq(collection.definitions.size)
77
+ end
78
+ end
79
+
80
+ describe "#add_definition" do
81
+ let(:collection) { described_class.new }
82
+ let(:definition) do
83
+ Lutaml::Model::Schema::Generator::Definition.new(String)
84
+ end
85
+
86
+ it "adds a definition to the collection" do
87
+ expect { collection.add_definition(definition) }
88
+ .to change { collection.definitions.size }.by(1)
89
+ end
90
+
91
+ it "accepts both Definition objects and classes" do
92
+ expect { collection.add_definition(String) }
93
+ .to change { collection.definitions.size }.by(1)
94
+ end
95
+ end
96
+
97
+ describe "#merge" do
98
+ let(:first_collection) do
99
+ described_class.new(
100
+ [
101
+ Lutaml::Model::Type::String,
102
+ Lutaml::Model::Type::Integer,
103
+ ],
104
+ )
105
+ end
106
+ let(:second_collection) do
107
+ described_class.new(
108
+ [
109
+ Lutaml::Model::Type::Float,
110
+ Lutaml::Model::Type::Boolean,
111
+ ],
112
+ )
113
+ end
114
+
115
+ it "merges two collections" do
116
+ first_collection.merge(second_collection)
117
+ expect(first_collection.definitions.size).to eq(4)
118
+ end
119
+ end
120
+ end