lutaml-model 0.3.24 → 0.3.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +36 -17
  3. data/README.adoc +396 -30
  4. data/lib/lutaml/model/attribute.rb +52 -27
  5. data/lib/lutaml/model/error/pattern_not_matched_error.rb +17 -0
  6. data/lib/lutaml/model/error/type_error.rb +9 -0
  7. data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
  8. data/lib/lutaml/model/error/validation_error.rb +0 -1
  9. data/lib/lutaml/model/error.rb +3 -0
  10. data/lib/lutaml/model/mapping_hash.rb +8 -0
  11. data/lib/lutaml/model/serialize.rb +10 -5
  12. data/lib/lutaml/model/type/boolean.rb +38 -0
  13. data/lib/lutaml/model/type/date.rb +35 -0
  14. data/lib/lutaml/model/type/date_time.rb +32 -4
  15. data/lib/lutaml/model/type/decimal.rb +42 -0
  16. data/lib/lutaml/model/type/float.rb +37 -0
  17. data/lib/lutaml/model/type/hash.rb +62 -0
  18. data/lib/lutaml/model/type/integer.rb +41 -0
  19. data/lib/lutaml/model/type/string.rb +49 -0
  20. data/lib/lutaml/model/type/time.rb +49 -0
  21. data/lib/lutaml/model/type/time_without_date.rb +37 -5
  22. data/lib/lutaml/model/type/value.rb +52 -0
  23. data/lib/lutaml/model/type.rb +50 -114
  24. data/lib/lutaml/model/validation.rb +2 -1
  25. data/lib/lutaml/model/version.rb +1 -1
  26. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +12 -3
  27. data/lib/lutaml/model/xml_adapter/builder/ox.rb +7 -1
  28. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +3 -1
  29. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +12 -8
  30. data/lib/lutaml/model/xml_adapter/xml_document.rb +6 -8
  31. data/lib/lutaml/model/xml_mapping.rb +6 -2
  32. data/lib/lutaml/model/xml_mapping_rule.rb +7 -1
  33. data/lutaml-model.gemspec +1 -1
  34. data/spec/address_spec.rb +170 -0
  35. data/spec/fixtures/address.rb +33 -0
  36. data/spec/fixtures/person.rb +73 -0
  37. data/spec/fixtures/sample_model.rb +40 -0
  38. data/spec/fixtures/vase.rb +38 -0
  39. data/spec/fixtures/xml/special_char.xml +13 -0
  40. data/spec/lutaml/model/attribute_spec.rb +139 -0
  41. data/spec/lutaml/model/cdata_spec.rb +520 -0
  42. data/spec/lutaml/model/collection_spec.rb +299 -0
  43. data/spec/lutaml/model/comparable_model_spec.rb +106 -0
  44. data/spec/lutaml/model/custom_model_spec.rb +410 -0
  45. data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
  46. data/spec/lutaml/model/defaults_spec.rb +221 -0
  47. data/spec/lutaml/model/delegation_spec.rb +340 -0
  48. data/spec/lutaml/model/inheritance_spec.rb +92 -0
  49. data/spec/lutaml/model/json_adapter_spec.rb +37 -0
  50. data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
  51. data/spec/lutaml/model/map_content_spec.rb +118 -0
  52. data/spec/lutaml/model/mixed_content_spec.rb +625 -0
  53. data/spec/lutaml/model/namespace_spec.rb +57 -0
  54. data/spec/lutaml/model/ordered_content_spec.rb +83 -0
  55. data/spec/lutaml/model/render_nil_spec.rb +138 -0
  56. data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
  57. data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
  58. data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
  59. data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
  60. data/spec/lutaml/model/serializable_spec.rb +297 -0
  61. data/spec/lutaml/model/serializable_validation_spec.rb +90 -0
  62. data/spec/lutaml/model/simple_model_spec.rb +314 -0
  63. data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
  64. data/spec/lutaml/model/type/boolean_spec.rb +54 -0
  65. data/spec/lutaml/model/type/date_spec.rb +118 -0
  66. data/spec/lutaml/model/type/date_time_spec.rb +127 -0
  67. data/spec/lutaml/model/type/decimal_spec.rb +125 -0
  68. data/spec/lutaml/model/type/float_spec.rb +191 -0
  69. data/spec/lutaml/model/type/hash_spec.rb +63 -0
  70. data/spec/lutaml/model/type/integer_spec.rb +145 -0
  71. data/spec/lutaml/model/type/string_spec.rb +150 -0
  72. data/spec/lutaml/model/type/time_spec.rb +142 -0
  73. data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
  74. data/spec/lutaml/model/type_spec.rb +276 -0
  75. data/spec/lutaml/model/utils_spec.rb +79 -0
  76. data/spec/lutaml/model/validation_spec.rb +83 -0
  77. data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
  78. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
  79. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
  80. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
  81. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
  82. data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
  83. data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
  84. data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
  85. data/spec/lutaml/model_spec.rb +1 -0
  86. data/spec/person_spec.rb +161 -0
  87. data/spec/spec_helper.rb +33 -0
  88. metadata +68 -2
@@ -0,0 +1,276 @@
1
+ # spec/lutaml/model/type_spec.rb
2
+ require "spec_helper"
3
+ require "bigdecimal"
4
+
5
+ # Test classes for type registration scenarios
6
+ class CustomType < Lutaml::Model::Type::Value
7
+ def self.cast(value)
8
+ value.to_s.upcase
9
+ end
10
+ end
11
+
12
+ class InvalidType
13
+ def self.cast(value)
14
+ value
15
+ end
16
+ end
17
+
18
+ RSpec.describe Lutaml::Model::Type do
19
+ describe "Type System" do
20
+ describe ".register and .lookup" do
21
+ context "with valid types" do
22
+ it "registers and looks up a custom type" do
23
+ described_class.register(:custom, CustomType)
24
+ expect(described_class.lookup(:custom)).to eq(CustomType)
25
+ end
26
+
27
+ it "allows overriding an existing type registration" do
28
+ described_class.register(:custom, CustomType)
29
+ replacement_type = Class.new(Lutaml::Model::Type::Value)
30
+ described_class.register(:custom, replacement_type)
31
+ expect(described_class.lookup(:custom)).to eq(replacement_type)
32
+ end
33
+ end
34
+
35
+ context "with invalid types" do
36
+ it "raises TypeError when registering non-Type::Value class" do
37
+ expect do
38
+ described_class.register(:invalid,
39
+ InvalidType)
40
+ end.to raise_error(Lutaml::Model::TypeError,
41
+ /not a valid Lutaml::Model::Type::Value/)
42
+ end
43
+
44
+ it "raises UnknownTypeError when looking up unregistered type" do
45
+ expect do
46
+ described_class.lookup(:nonexistent)
47
+ end.to raise_error(
48
+ Lutaml::Model::UnknownTypeError, /Unknown type 'nonexistent'/
49
+ )
50
+ end
51
+ end
52
+ end
53
+
54
+ describe "Built-in Types" do
55
+ before do
56
+ described_class.register_builtin_types
57
+ end
58
+
59
+ after do
60
+ described_class.instance_variable_set(:@registry, nil)
61
+ end
62
+
63
+ let(:built_in_types) do
64
+ {
65
+ string: Lutaml::Model::Type::String,
66
+ integer: Lutaml::Model::Type::Integer,
67
+ float: Lutaml::Model::Type::Float,
68
+ date: Lutaml::Model::Type::Date,
69
+ time: Lutaml::Model::Type::Time,
70
+ date_time: Lutaml::Model::Type::DateTime,
71
+ time_without_date: Lutaml::Model::Type::TimeWithoutDate,
72
+ boolean: Lutaml::Model::Type::Boolean,
73
+ hash: Lutaml::Model::Type::Hash,
74
+ }
75
+ end
76
+
77
+ it "has all built-in types registered" do
78
+ built_in_types.each do |type_name, type_class|
79
+ puts described_class
80
+ expect(described_class.lookup(type_name)).to eq(type_class)
81
+ end
82
+ end
83
+
84
+ describe "Type Casting" do
85
+ let(:test_date) { Date.new(2024, 1, 1) }
86
+ let(:test_time) { Time.new(2024, 1, 1, 12, 0, 0) }
87
+ let(:test_date_time) { DateTime.new(2024, 1, 1, 12, 0, 0) }
88
+
89
+ {
90
+ Lutaml::Model::Type::String => { input: 123, expected: "123" },
91
+ Lutaml::Model::Type::Integer => { input: "123", expected: 123 },
92
+ Lutaml::Model::Type::Float => { input: "123.45", expected: 123.45 },
93
+ Lutaml::Model::Type::Date => { input: "2024-01-01",
94
+ expected: Date.new(2024, 1, 1) },
95
+ Lutaml::Model::Type::Time => { input: "2024-01-01T12:00:00",
96
+ expected_hour: 12 },
97
+ Lutaml::Model::Type::DateTime => { input: "2024-01-01T12:00:00",
98
+ expected: DateTime.new(2024, 1, 1,
99
+ 12, 0, 0) },
100
+ Lutaml::Model::Type::Boolean => { input: "true", expected: true },
101
+ Lutaml::Model::Type::Hash => { input: { key: "value" },
102
+ expected: { key: "value" } },
103
+ }.each do |type_class, test_data|
104
+ it "correctly casts #{type_class}" do
105
+ result = type_class.cast(test_data[:input])
106
+ if test_data.key?(:expected_hour)
107
+ expect(result.hour).to eq(test_data[:expected_hour])
108
+ else
109
+ expect(result).to eq(test_data[:expected])
110
+ end
111
+ end
112
+ end
113
+
114
+ it "handles nil values gracefully" do
115
+ built_in_types.each_value do |type_class|
116
+ expect(type_class.cast(nil)).to be_nil
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "Decimal Type" do
123
+ context "when BigDecimal is available" do
124
+ before do
125
+ require "bigdecimal"
126
+ described_class.register_builtin_types
127
+ end
128
+
129
+ it "registers and uses Decimal type" do
130
+ expect(described_class.lookup(:decimal)).to eq(Lutaml::Model::Type::Decimal)
131
+ expect(Lutaml::Model::Type::Decimal.cast("123.45")).to eq(BigDecimal("123.45"))
132
+ end
133
+
134
+ it "serializes decimal values correctly" do
135
+ value = BigDecimal("123.45")
136
+ expect(Lutaml::Model::Type::Decimal.serialize(value)).to eq("123.45")
137
+ end
138
+ end
139
+
140
+ context "when BigDecimal is not available" do
141
+ before do
142
+ Object.send(:remove_const, :BigDecimal) if defined?(BigDecimal)
143
+ $LOADED_FEATURES.delete_if { |path| path.include?("bigdecimal") }
144
+ end
145
+
146
+ after do
147
+ require "bigdecimal"
148
+ end
149
+
150
+ let(:decimal_class) { described_class.lookup(:decimal) }
151
+
152
+ it "raises TypeNotEnabledError when using Decimal type" do
153
+ expect do
154
+ described_class.lookup(:decimal).cast("123.45")
155
+ end.to raise_error(Lutaml::Model::TypeNotEnabledError)
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ describe "Type Usage in Models" do
162
+ class TypeTestModel < Lutaml::Model::Serializable
163
+ attribute :string_symbol, :string
164
+ attribute :string_class, Lutaml::Model::Type::String
165
+ attribute :integer_value, :integer
166
+ attribute :float_value, :float
167
+ attribute :date_value, :date
168
+ attribute :time_value, :time
169
+ attribute :time_without_date_value, :time_without_date
170
+ attribute :date_time_value, :date_time
171
+ attribute :boolean_value, :boolean
172
+ attribute :hash_value, :hash
173
+
174
+ xml do
175
+ root "test"
176
+ map_element "string_symbol", to: :string_symbol
177
+ map_element "string_class", to: :string_class
178
+ map_element "integer", to: :integer_value
179
+ map_element "float", to: :float_value
180
+ map_element "date", to: :date_value
181
+ map_element "time", to: :time_value
182
+ map_element "time_without_date", to: :time_without_date_value
183
+ map_element "date_time", to: :date_time_value
184
+ map_element "boolean", to: :boolean_value
185
+ map_element "hash", to: :hash_value
186
+ end
187
+ end
188
+
189
+ let(:test_instance) do
190
+ TypeTestModel.new(
191
+ string_symbol: "test",
192
+ string_class: "test",
193
+ integer_value: "123",
194
+ float_value: "123.45",
195
+ date_value: "2024-01-01",
196
+ time_value: "12:00:00",
197
+ time_without_date_value: "10:06:15",
198
+ date_time_value: "2024-01-01T12:00:00",
199
+ boolean_value: "true",
200
+ hash_value: { key: "value" },
201
+ )
202
+ end
203
+
204
+ describe "Type Casting in Models" do
205
+ it "correctly casts values using both symbol and class-based types" do
206
+ expect(test_instance.string_symbol).to eq("test")
207
+ expect(test_instance.string_class).to eq("test")
208
+ expect(test_instance.integer_value).to eq(123)
209
+ expect(test_instance.float_value).to eq(123.45)
210
+ expect(test_instance.date_value).to eq(Date.new(2024, 1, 1))
211
+ expect(test_instance.time_value.hour).to eq(12)
212
+ expect(test_instance.date_time_value).to eq(DateTime.new(2024, 1, 1,
213
+ 12, 0, 0))
214
+ expect(test_instance.boolean_value).to be(true)
215
+ expect(test_instance.hash_value).to eq({ key: "value" })
216
+ end
217
+
218
+ it "produces identical results with symbol and class-based definitions" do
219
+ expect(test_instance.string_symbol).to eq(test_instance.string_class)
220
+ end
221
+ end
222
+
223
+ describe "Serialization" do
224
+ it "correctly serializes to XML" do
225
+ expected_xml = <<~XML
226
+ <test>
227
+ <string_symbol>test</string_symbol>
228
+ <string_class>test</string_class>
229
+ <integer>123</integer>
230
+ <float>123.45</float>
231
+ <date>2024-01-01</date>
232
+ <time>#{Time.parse('12:00:00').iso8601}</time>
233
+ <time_without_date>10:06:15</time_without_date>
234
+ <date_time>2024-01-01T12:00:00+00:00</date_time>
235
+ <boolean>true</boolean>
236
+ <hash>
237
+ <key>value</key>
238
+ </hash>
239
+ </test>
240
+ XML
241
+
242
+ expect(test_instance.to_xml).to be_equivalent_to(expected_xml)
243
+ end
244
+
245
+ it "correctly deserializes from XML" do
246
+ xml = <<~XML
247
+ <test>
248
+ <string_symbol>test</string_symbol>
249
+ <string_class>test</string_class>
250
+ <integer>123</integer>
251
+ <float>123.45</float>
252
+ <date>2024-01-01</date>
253
+ <time>12:00:00</time>
254
+ <time_without_date>10:06:15</time_without_date>
255
+ <date_time>2024-01-01T12:00:00</date_time>
256
+ <boolean>true</boolean>
257
+ <hash>
258
+ <key>value</key>
259
+ </hash>
260
+ </test>
261
+ XML
262
+
263
+ deserialized = TypeTestModel.from_xml(xml)
264
+ expect(deserialized.string_symbol).to eq("test")
265
+ expect(deserialized.string_class).to eq("test")
266
+ expect(deserialized.integer_value).to eq(123)
267
+ expect(deserialized.float_value).to eq(123.45)
268
+ expect(deserialized.date_value).to eq(Date.new(2024, 1, 1))
269
+ expect(deserialized.date_time_value).to eq(DateTime.new(2024, 1, 1, 12,
270
+ 0, 0))
271
+ expect(deserialized.boolean_value).to be(true)
272
+ expect(deserialized.hash_value).to eq({ "key" => "value" })
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+ require "lutaml/model/utils"
3
+
4
+ RSpec.describe Lutaml::Model::Utils do
5
+ let(:utils) { described_class }
6
+
7
+ shared_examples "string conversion" do |method, examples|
8
+ describe ".#{method}" do
9
+ examples.each do |input, expected_output|
10
+ context "when input is #{input.nil? ? 'nil' : "'#{input}'"}" do
11
+ it "returns '#{expected_output}'" do
12
+ expect(utils.send(method, input)).to eq(expected_output)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ camel_case_examples = {
20
+ "hello_world" => "HelloWorld",
21
+ "foo_bar_baz" => "FooBarBaz",
22
+ "" => "",
23
+ nil => "",
24
+ "hello_world/foo_bar_baz" => "HelloWorld::FooBarBaz",
25
+ }
26
+
27
+ classify_examples_extra = {
28
+ "hello_world::foo_bar_baz" => "HelloWorld::FooBarBaz",
29
+ }
30
+ classify_examples = camel_case_examples.merge(classify_examples_extra)
31
+
32
+ snake_case_examples = {
33
+ "HelloWorld" => "hello_world",
34
+ "FooBarBaz" => "foo_bar_baz",
35
+ "" => "",
36
+ nil => "",
37
+ "HelloWorld::FooBarBaz" => "hello_world/foo_bar_baz",
38
+ }
39
+
40
+ include_examples "string conversion", :camel_case, camel_case_examples
41
+ include_examples "string conversion", :classify, classify_examples
42
+ include_examples "string conversion", :snake_case, snake_case_examples
43
+
44
+ describe ".deep_dup" do
45
+ let(:original_hash) do
46
+ {
47
+ one: {
48
+ one_one: {
49
+ one_one1: "one",
50
+ one_one2: :two,
51
+ },
52
+ one_two: "12",
53
+ },
54
+ }
55
+ end
56
+
57
+ let(:duplicate_hash) { utils.deep_dup(original_hash) }
58
+
59
+ it "creates deep duplicate of hash" do
60
+ expect(original_hash).to eq(duplicate_hash)
61
+ expect(original_hash.object_id).not_to eq(duplicate_hash.object_id)
62
+
63
+ expect(original_hash[:one]).to eq(duplicate_hash[:one])
64
+ expect(original_hash[:one].object_id).not_to eq(duplicate_hash[:one].object_id)
65
+
66
+ expect(original_hash[:one][:one_one]).to eq(duplicate_hash[:one][:one_one])
67
+ expect(original_hash[:one][:one_one].object_id).not_to eq(duplicate_hash[:one][:one_one].object_id)
68
+
69
+ expect(original_hash[:one][:one_one][:one_one1]).to eq(duplicate_hash[:one][:one_one][:one_one1])
70
+ expect(original_hash[:one][:one_one][:one_one1].object_id).not_to eq(duplicate_hash[:one][:one_one][:one_one1].object_id)
71
+
72
+ # this is a symbol so the object_id will be same
73
+ expect(original_hash[:one][:one_one][:one_one2]).to eq(duplicate_hash[:one][:one_one][:one_one2])
74
+
75
+ expect(original_hash[:one][:one_two]).to eq(duplicate_hash[:one][:one_two])
76
+ expect(original_hash[:one][:one_two].object_id).not_to eq(duplicate_hash[:one][:one_two].object_id)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,83 @@
1
+ # spec/lutaml/model/validation_spec.rb
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Lutaml::Model::Validation do
6
+ class ValidationTestClass < Lutaml::Model::Serializable
7
+ attribute :name, :string
8
+ attribute :age, :integer
9
+ attribute :email, :string, values: ["test@example.com", "user@example.com"]
10
+ attribute :tags, :string, collection: true
11
+ attribute :role, :string, collection: 1..3
12
+ end
13
+
14
+ let(:valid_instance) do
15
+ ValidationTestClass.new(
16
+ name: "John Doe",
17
+ age: 30,
18
+ email: "test@example.com",
19
+ tags: ["tag1", "tag2"],
20
+ role: ["admin"],
21
+ )
22
+ end
23
+
24
+ describe "#validate" do
25
+ it "returns an empty array for a valid instance" do
26
+ expect(valid_instance.validate).to be_empty
27
+ end
28
+
29
+ xit "returns errors for invalid integer value" do
30
+ instance = ValidationTestClass.new(age: "thirty", role: ["admin"])
31
+ errors = instance.validate
32
+ expect(errors).to include("Invalid value for attribute age: thirty")
33
+ end
34
+
35
+ it "returns errors for value not in allowed set" do
36
+ instance = ValidationTestClass.new(email: "invalid@example.com",
37
+ role: ["admin"])
38
+ expect do
39
+ instance.validate!
40
+ end.to raise_error(Lutaml::Model::ValidationError) do |error|
41
+ expect(error.error_messages.join("\n")).to include("email is `invalid@example.com`, must be one of the following [test@example.com, user@example.com]")
42
+ end
43
+ end
44
+
45
+ it "returns errors for invalid collection count" do
46
+ instance = ValidationTestClass.new(role: ["admin", "user", "manager",
47
+ "guest"])
48
+ expect do
49
+ instance.validate!
50
+ end.to raise_error(Lutaml::Model::ValidationError) do |error|
51
+ expect(error.error_messages.join("\n")).to include("role count is 4, must be between 1 and 3")
52
+ end
53
+ end
54
+
55
+ xit "returns multiple errors for multiple invalid attributes" do
56
+ instance = ValidationTestClass.new(name: "123", age: "thirty",
57
+ email: "invalid@example.com", role: [])
58
+ expect do
59
+ instance.validate!
60
+ end.to raise_error(Lutaml::Model::ValidationError) do |error|
61
+ expect(error.error_messages.join("\n")).to include("Invalid value for attribute age: thirty")
62
+ expect(error.error_messages.join("\n")).to include("email is `invalid@example.com`, must be one of the following [test@example.com, user@example.com]")
63
+ expect(error.error_messages.join("\n")).to include("role count is 0, must be between 1 and 3")
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "#validate!" do
69
+ it "does not raise an error for a valid instance" do
70
+ expect { valid_instance.validate! }.not_to raise_error
71
+ end
72
+
73
+ xit "raises a ValidationError with all error messages for an invalid instance" do
74
+ instance = ValidationTestClass.new(name: "test", age: "thirty")
75
+ expect do
76
+ instance.validate!
77
+ end.to raise_error(Lutaml::Model::ValidationError) do |error|
78
+ expect(error.error_messages.join("\n")).to include("role count is 0, must be between 1 and 3")
79
+ expect(error.error_messages.join("\n")).to include("Invalid value for attribute age: thirty")
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ChildMapping
4
+ class Schema < Lutaml::Model::Serializable
5
+ attribute :id, Lutaml::Model::Type::String
6
+ attribute :path, Lutaml::Model::Type::String
7
+ attribute :name, Lutaml::Model::Type::String
8
+ end
9
+
10
+ class ChildMappingClass < Lutaml::Model::Serializable
11
+ attribute :schemas, Schema, collection: true
12
+
13
+ json do
14
+ map "schemas", to: :schemas,
15
+ child_mappings: {
16
+ id: :key,
17
+ path: %i[path link],
18
+ name: %i[path name],
19
+ }
20
+ end
21
+
22
+ yaml do
23
+ map "schemas", to: :schemas,
24
+ child_mappings: {
25
+ id: :key,
26
+ path: %i[path link],
27
+ name: %i[path name],
28
+ }
29
+ end
30
+
31
+ toml do
32
+ map "schemas", to: :schemas,
33
+ child_mappings: {
34
+ id: :key,
35
+ path: %i[path abc],
36
+ name: %i[path name],
37
+ }
38
+ end
39
+ end
40
+ end
41
+
42
+ RSpec.describe ChildMapping do
43
+ let(:mapper) { ChildMapping::ChildMappingClass }
44
+ let(:schema) { ChildMapping::Schema }
45
+
46
+ let(:hash) do
47
+ {
48
+ "schemas" => {
49
+ "foo" => {
50
+ "path" => {
51
+ "link" => "link one",
52
+ "name" => "one",
53
+ },
54
+ },
55
+ "abc" => {
56
+ "path" => {
57
+ "link" => "link two",
58
+ "name" => "two",
59
+ },
60
+ },
61
+ "hello" => {
62
+ "path" => {
63
+ "link" => "link three",
64
+ "name" => "three",
65
+ },
66
+ },
67
+ },
68
+ }
69
+ end
70
+
71
+ let(:expected_ids) { ["foo", "abc", "hello"] }
72
+ let(:expected_paths) { ["link one", "link two", "link three"] }
73
+ let(:expected_names) { ["one", "two", "three"] }
74
+
75
+ context "with json" do
76
+ let(:json) do
77
+ hash.to_json
78
+ end
79
+
80
+ describe ".from_json" do
81
+ it "create model according to json" do
82
+ instance = mapper.from_json(json)
83
+
84
+ expect(instance.schemas.count).to eq(3)
85
+ expect(instance.schemas.map(&:id)).to eq(expected_ids)
86
+ expect(instance.schemas.map(&:path)).to eq(expected_paths)
87
+ expect(instance.schemas.map(&:name)).to eq(expected_names)
88
+ end
89
+ end
90
+
91
+ describe ".to_json" do
92
+ it "converts objects to json" do
93
+ schema1 = schema.new(id: "foo", path: "link one", name: "one")
94
+ schema2 = schema.new(id: "abc", path: "link two", name: "two")
95
+ schema3 = schema.new(id: "hello", path: "link three", name: "three")
96
+
97
+ instance = mapper.new(schemas: [schema1, schema2, schema3])
98
+
99
+ expect(instance.to_json).to eq(json)
100
+ end
101
+ end
102
+ end
103
+
104
+ context "with yaml" do
105
+ let(:yaml) do
106
+ hash.to_yaml
107
+ end
108
+
109
+ describe ".from_yaml" do
110
+ it "create model according to yaml" do
111
+ instance = mapper.from_yaml(yaml)
112
+
113
+ expect(instance.schemas.count).to eq(3)
114
+ expect(instance.schemas.map(&:id)).to eq(expected_ids)
115
+ expect(instance.schemas.map(&:path)).to eq(expected_paths)
116
+ expect(instance.schemas.map(&:name)).to eq(expected_names)
117
+ end
118
+ end
119
+
120
+ describe ".to_yaml" do
121
+ it "converts objects to yaml" do
122
+ schema1 = schema.new(id: "foo", path: "link one", name: "one")
123
+ schema2 = schema.new(id: "abc", path: "link two", name: "two")
124
+ schema3 = schema.new(id: "hello", path: "link three", name: "three")
125
+
126
+ instance = mapper.new(schemas: [schema1, schema2, schema3])
127
+
128
+ expect(instance.to_yaml).to eq(yaml)
129
+ end
130
+ end
131
+ end
132
+
133
+ context "with toml" do
134
+ let(:toml) do
135
+ <<~TOML
136
+ [schemas.foo.path]
137
+ abc = "link one"
138
+ name = "one"
139
+ [schemas.abc.path]
140
+ abc = "link two"
141
+ name = "two"
142
+ [schemas.hello.path]
143
+ abc = "link three"
144
+ name = "three"
145
+ TOML
146
+ end
147
+
148
+ describe ".from_toml" do
149
+ it "create model according to toml" do
150
+ instance = mapper.from_toml(toml)
151
+
152
+ expect(instance.schemas.count).to eq(3)
153
+ expect(instance.schemas.map(&:id)).to eq(expected_ids)
154
+ expect(instance.schemas.map(&:path)).to eq(expected_paths)
155
+ expect(instance.schemas.map(&:name)).to eq(expected_names)
156
+ end
157
+ end
158
+
159
+ describe ".to_toml" do
160
+ it "converts objects to toml" do
161
+ schema1 = schema.new(id: "foo", path: "link one", name: "one")
162
+ schema2 = schema.new(id: "abc", path: "link two", name: "two")
163
+ schema3 = schema.new(id: "hello", path: "link three", name: "three")
164
+
165
+ instance = mapper.new(schemas: [schema1, schema2, schema3])
166
+
167
+ actual = Lutaml::Model::Config.toml_adapter.parse(instance.to_toml)
168
+ expected = Lutaml::Model::Config.toml_adapter.parse(toml)
169
+
170
+ expect(actual.attributes).to eq(expected.attributes)
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,56 @@
1
+ require "spec_helper"
2
+ require "nokogiri"
3
+ require_relative "../../../../lib/lutaml/model/xml_adapter/nokogiri_adapter"
4
+
5
+ RSpec.describe Lutaml::Model::XmlAdapter::NokogiriAdapter do
6
+ let(:xml_string) do
7
+ <<-XML
8
+ <root xmlns="http://example.com/default" xmlns:prefix="http://example.com/prefixed">
9
+ <prefix:child attr="value" prefix:attr1="prefixed_value">Text</prefix:child>
10
+ </root>
11
+ XML
12
+ end
13
+
14
+ let(:document) { described_class.parse(xml_string) }
15
+
16
+ context "when parsing XML with namespaces" do
17
+ let(:child) { document.root.children[1] }
18
+
19
+ it "parses the root element with default namespace" do
20
+ expect(document.root.name).to eq("root")
21
+ expect(document.root.namespace.uri).to eq("http://example.com/default")
22
+ expect(document.root.namespace.prefix).to be_nil
23
+ end
24
+
25
+ it "parses child element with prefixed namespace" do
26
+ expect(child.name).to eq("prefix:child")
27
+ expect(child.namespace.uri).to eq("http://example.com/prefixed")
28
+ expect(child.namespace.prefix).to eq("prefix")
29
+ end
30
+
31
+ it "parses attributes with and without namespaces" do
32
+ expect(child.attributes["attr"].value).to eq("value")
33
+ expect(child.attributes["attr"].namespace).to be_nil
34
+ expect(child.attributes["prefix:attr1"].value).to eq("prefixed_value")
35
+ expect(child.attributes["prefix:attr1"].namespace).to eq("http://example.com/prefixed")
36
+ expect(child.attributes["prefix:attr1"].namespace_prefix).to eq("prefix")
37
+ end
38
+ end
39
+
40
+ context "when generating XML with namespaces" do
41
+ it "generates XML with namespaces correctly" do
42
+ xml_output = document.to_xml
43
+ parsed_output = Nokogiri::XML(xml_output)
44
+
45
+ root = parsed_output.root
46
+ expect(root.name).to eq("root")
47
+ expect(root.namespace.href).to eq("http://example.com/default")
48
+
49
+ child = root.children[1]
50
+ expect(child.name).to eq("child")
51
+ expect(child.namespace.href).to eq("http://example.com/prefixed")
52
+ expect(child.attributes["attr"].value).to eq("value")
53
+ expect(child.attributes["attr1"].value).to eq("prefixed_value")
54
+ end
55
+ end
56
+ end