lutaml-model 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dependent-tests.yml +2 -0
  3. data/.rubocop_todo.yml +86 -23
  4. data/Gemfile +2 -0
  5. data/README.adoc +1441 -220
  6. data/lib/lutaml/model/attribute.rb +33 -10
  7. data/lib/lutaml/model/choice.rb +56 -0
  8. data/lib/lutaml/model/config.rb +1 -0
  9. data/lib/lutaml/model/constants.rb +7 -0
  10. data/lib/lutaml/model/error/choice_lower_bound_error.rb +9 -0
  11. data/lib/lutaml/model/error/choice_upper_bound_error.rb +9 -0
  12. data/lib/lutaml/model/error/import_model_with_root_error.rb +9 -0
  13. data/lib/lutaml/model/error/incorrect_sequence_error.rb +9 -0
  14. data/lib/lutaml/model/error/invalid_choice_range_error.rb +20 -0
  15. data/lib/lutaml/model/error/no_root_mapping_error.rb +9 -0
  16. data/lib/lutaml/model/error/no_root_namespace_error.rb +9 -0
  17. data/lib/lutaml/model/error/type/invalid_value_error.rb +19 -0
  18. data/lib/lutaml/model/error/unknown_sequence_mapping_error.rb +9 -0
  19. data/lib/lutaml/model/error.rb +9 -0
  20. data/lib/lutaml/model/json_adapter/standard_json_adapter.rb +6 -1
  21. data/lib/lutaml/model/key_value_mapping.rb +34 -3
  22. data/lib/lutaml/model/key_value_mapping_rule.rb +4 -2
  23. data/lib/lutaml/model/liquefiable.rb +59 -0
  24. data/lib/lutaml/model/mapping_hash.rb +9 -1
  25. data/lib/lutaml/model/mapping_rule.rb +19 -2
  26. data/lib/lutaml/model/schema/templates/simple_type.rb +247 -0
  27. data/lib/lutaml/model/schema/xml_compiler.rb +762 -0
  28. data/lib/lutaml/model/schema.rb +5 -0
  29. data/lib/lutaml/model/schema_location.rb +7 -0
  30. data/lib/lutaml/model/sequence.rb +71 -0
  31. data/lib/lutaml/model/serialize.rb +139 -33
  32. data/lib/lutaml/model/toml_adapter/toml_rb_adapter.rb +1 -2
  33. data/lib/lutaml/model/type/decimal.rb +0 -4
  34. data/lib/lutaml/model/type/hash.rb +11 -11
  35. data/lib/lutaml/model/type/time.rb +3 -3
  36. data/lib/lutaml/model/utils.rb +19 -15
  37. data/lib/lutaml/model/validation.rb +12 -1
  38. data/lib/lutaml/model/version.rb +1 -1
  39. data/lib/lutaml/model/xml_adapter/builder/oga.rb +10 -7
  40. data/lib/lutaml/model/xml_adapter/builder/ox.rb +20 -13
  41. data/lib/lutaml/model/xml_adapter/element.rb +32 -0
  42. data/lib/lutaml/model/xml_adapter/nokogiri_adapter.rb +13 -9
  43. data/lib/lutaml/model/xml_adapter/oga/element.rb +14 -13
  44. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +86 -19
  45. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +19 -15
  46. data/lib/lutaml/model/xml_adapter/xml_document.rb +82 -25
  47. data/lib/lutaml/model/xml_adapter/xml_element.rb +57 -3
  48. data/lib/lutaml/model/xml_mapping.rb +53 -9
  49. data/lib/lutaml/model/xml_mapping_rule.rb +8 -6
  50. data/lib/lutaml/model.rb +2 -0
  51. data/lutaml-model.gemspec +5 -0
  52. data/spec/benchmarks/xml_parsing_benchmark_spec.rb +75 -0
  53. data/spec/ceramic_spec.rb +39 -0
  54. data/spec/fixtures/ceramic.rb +23 -0
  55. data/spec/fixtures/xml/address_example_260.xsd +9 -0
  56. data/spec/fixtures/xml/invalid_math_document.xml +4 -0
  57. data/spec/fixtures/xml/math_document_schema.xsd +56 -0
  58. data/spec/fixtures/xml/test_schema.xsd +53 -0
  59. data/spec/fixtures/xml/user.xsd +10 -0
  60. data/spec/fixtures/xml/valid_math_document.xml +4 -0
  61. data/spec/lutaml/model/cdata_spec.rb +4 -5
  62. data/spec/lutaml/model/choice_spec.rb +168 -0
  63. data/spec/lutaml/model/collection_spec.rb +1 -1
  64. data/spec/lutaml/model/custom_model_spec.rb +7 -21
  65. data/spec/lutaml/model/custom_serialization_spec.rb +74 -2
  66. data/spec/lutaml/model/defaults_spec.rb +3 -1
  67. data/spec/lutaml/model/delegation_spec.rb +7 -5
  68. data/spec/lutaml/model/enum_spec.rb +35 -0
  69. data/spec/lutaml/model/group_spec.rb +160 -0
  70. data/spec/lutaml/model/inheritance_spec.rb +25 -0
  71. data/spec/lutaml/model/key_value_mapping_spec.rb +27 -0
  72. data/spec/lutaml/model/liquefiable_spec.rb +121 -0
  73. data/spec/lutaml/model/map_all_spec.rb +188 -0
  74. data/spec/lutaml/model/mixed_content_spec.rb +95 -56
  75. data/spec/lutaml/model/multiple_mapping_spec.rb +22 -10
  76. data/spec/lutaml/model/schema/xml_compiler_spec.rb +1624 -0
  77. data/spec/lutaml/model/sequence_spec.rb +216 -0
  78. data/spec/lutaml/model/transformation_spec.rb +230 -0
  79. data/spec/lutaml/model/type_spec.rb +138 -31
  80. data/spec/lutaml/model/utils_spec.rb +32 -0
  81. data/spec/lutaml/model/with_child_mapping_spec.rb +2 -2
  82. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -7
  83. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +52 -0
  84. data/spec/lutaml/model/xml_mapping_rule_spec.rb +51 -0
  85. data/spec/lutaml/model/xml_mapping_spec.rb +250 -112
  86. metadata +77 -2
@@ -0,0 +1,216 @@
1
+ require "spec_helper"
2
+ require "lutaml/model"
3
+
4
+ module SequenceSpec
5
+ class Ceramic < Lutaml::Model::Serializable
6
+ attribute :id, :string
7
+ attribute :name, :string
8
+ attribute :type, :string
9
+ attribute :color, :string
10
+ attribute :bold, :string
11
+ attribute :text, :string
12
+ attribute :usage, :string
13
+ attribute :size, :string
14
+ attribute :first_name, :string
15
+ attribute :last_name, :string
16
+ attribute :tag, :string
17
+ attribute :temperature, :string
18
+
19
+ xml do
20
+ root "Ceramic"
21
+ map_element :tag, to: :tag
22
+
23
+ sequence do
24
+ map_element :id, to: :id
25
+ map_element :name, to: :name
26
+ map_element :type, to: :type
27
+ map_element :color, to: :color
28
+ map_element :bold, to: :bold
29
+ map_element :text, to: :text
30
+ sequence do
31
+ map_element :usage, to: :usage
32
+ map_element :size, to: :size
33
+ end
34
+ end
35
+
36
+ sequence do
37
+ map_element :first_name, to: :first_name
38
+ map_element :last_name, to: :last_name
39
+ end
40
+
41
+ map_element :temperature, to: :temperature
42
+ end
43
+ end
44
+
45
+ class CeramicCollection < Lutaml::Model::Serializable
46
+ attribute :ceramic, Ceramic, collection: 1..2
47
+
48
+ xml do
49
+ root "collection"
50
+ map_element "ceramic", to: :ceramic
51
+ end
52
+ end
53
+ end
54
+
55
+ RSpec.describe "Sequence" do
56
+ context "with nesting sequence" do
57
+ let(:mapper) { SequenceSpec::Ceramic }
58
+
59
+ it "don't raise error for a valid instance, if given attribute for sequence has correct order" do
60
+ xml = <<~XML
61
+ <Ceramic>
62
+ <tag>Nik</tag>
63
+ <id>1</id>
64
+ <name>Vase</name>
65
+ <type>Decorative</type>
66
+ <color>Blue</color>
67
+ <bold>Heading</bold>
68
+ <text>Header</text>
69
+ <usage>Indoor</usage>
70
+ <size>Medium</size>
71
+ <first_name>Dale</first_name>
72
+ <last_name>Steyn</last_name>
73
+ <temperature>Normal</temperature>
74
+ </Ceramic>
75
+ XML
76
+
77
+ expect { mapper.from_xml(xml) }.not_to raise_error
78
+ end
79
+
80
+ it "don't raise error for a valid instance with sequence range, if given attribute for sequence has correct order" do
81
+ xml = <<~XML
82
+ <collection>
83
+ <ceramic>
84
+ <tag>Nik</tag>
85
+ <id>1</id>
86
+ <name>Vase</name>
87
+ <type>Decorative</type>
88
+ <color>Blue</color>
89
+ <bold>Heading</bold>
90
+ <text>Header</text>
91
+ <usage>Indoor</usage>
92
+ <size>Medium</size>
93
+ <first_name>Dale</first_name>
94
+ <last_name>Steyn</last_name>
95
+ <temperature>Normal</temperature>
96
+ </ceramic>
97
+ <ceramic>
98
+ <tag>Nik</tag>
99
+ <id>1</id>
100
+ <name>Vase</name>
101
+ <type>Decorative</type>
102
+ <color>Blue</color>
103
+ <bold>Heading</bold>
104
+ <text>Header</text>
105
+ <usage>Indoor</usage>
106
+ <size>Medium</size>
107
+ <first_name>Dale</first_name>
108
+ <last_name>Steyn</last_name>
109
+ <temperature>Normal</temperature>
110
+ </ceramic>
111
+ </collection>
112
+ XML
113
+
114
+ expect do
115
+ SequenceSpec::CeramicCollection.from_xml(xml)
116
+ end.not_to raise_error
117
+ end
118
+
119
+ it "raises error, if given attributes order is incorrect in sequence" do
120
+ xml = <<~XML
121
+ <Ceramic>
122
+ <tag>Nik</tag>
123
+ <temperature>High</temperature>
124
+ <first_name>Micheal</first_name>
125
+ <id>1</id>
126
+ <name>Vase</name>
127
+ <type>Decorative</type>
128
+ <color>Blue</color>
129
+ <bold>Heading</bold>
130
+ <usage>Indoor</usage>
131
+ <size>Medium</size>
132
+ <last_name>Johnson</last_name>
133
+ <text>Header</text>
134
+ </Ceramic>
135
+ XML
136
+
137
+ expect do
138
+ mapper.from_xml(xml)
139
+ end.to raise_error(Lutaml::Model::IncorrectSequenceError) do |error|
140
+ expect(error.message).to eq("Element `usage` does not match the expected sequence order element `text`")
141
+ end
142
+ end
143
+
144
+ it "raises error with sequence range, if given attributes order is incorrect in sequence" do
145
+ invalid_xml = <<~XML
146
+ <collection>
147
+ <ceramic>
148
+ <id>1</id>
149
+ <name>Vase</name>
150
+ <type>Decorative</type>
151
+ <color>Blue</color>
152
+ <bold>Heading</bold>
153
+ <text>Header</text>
154
+ <usage>Indoor</usage>
155
+ <size>Medium</size>
156
+ <first_name>Dale</first_name>
157
+ <last_name>Steyn</last_name>
158
+ <temperature>Normal</temperature>
159
+ <tag>Nik</tag>
160
+ </ceramic>
161
+
162
+ <ceramic>
163
+ <id>2</id>
164
+ <name>Nick</name>
165
+ <type>Unique</type>
166
+ <color>Red</color>
167
+ <bold>Name</bold>
168
+ <text>Body</text>
169
+ <usage>Outdoor</usage>
170
+ <size>Small</size>
171
+ <first_name>Smith</first_name>
172
+ <last_name>Ash</last_name>
173
+ <temperature>High</temperature>
174
+ <tag>Adid</tag>
175
+ </ceramic>
176
+
177
+ <ceramic>
178
+ <id>3</id>
179
+ <name>Starc</name>
180
+ <type>Int</type>
181
+ <color>White</color>
182
+ <bold>Act</bold>
183
+ <text>Footer</text>
184
+ <usage>Nothing</usage>
185
+ <size>Large</size>
186
+ <first_name>Dale</first_name>
187
+ <last_name>Steyn</last_name>
188
+ <temperature>Normal</temperature>
189
+ <tag>Bet</tag>
190
+ </ceramic>
191
+ </collection>
192
+ XML
193
+
194
+ expect do
195
+ SequenceSpec::CeramicCollection.from_xml(invalid_xml).validate!
196
+ end.to raise_error(Lutaml::Model::ValidationError) do |error|
197
+ expect(error).to include(Lutaml::Model::CollectionCountOutOfRangeError)
198
+ expect(error.error_messages).to eq(["ceramic count is 3, must be between 1 and 2"])
199
+ end
200
+ end
201
+
202
+ it "raises error, if mapping other map_element are defined in sequence" do
203
+ expect do
204
+ Class.new(Lutaml::Model::Serializable) do
205
+ attribute :type, :string
206
+
207
+ xml do
208
+ sequence do
209
+ map_attribute :type, to: :type
210
+ end
211
+ end
212
+ end
213
+ end.to raise_error(Lutaml::Model::UnknownSequenceMappingError, "map_attribute is not allowed in sequence")
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,230 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe "Value Transformations" do
4
+ module TransformationSpec
5
+ # Class with only attribute-level transformations
6
+ class AttributeTransformPerson < Lutaml::Model::Serializable
7
+ attribute :name, :string, transform: {
8
+ export: ->(value) { value.to_s.upcase },
9
+ }
10
+ attribute :email, :string, transform: {
11
+ import: ->(value) { "#{value}@example.com" },
12
+ }
13
+ attribute :tags, :string, collection: true, transform: {
14
+ export: ->(value) { value.map(&:upcase) },
15
+ import: ->(value) { value.map { |v| "#{v}-1" } },
16
+ }
17
+ end
18
+
19
+ # Class with only mapping-level transformations
20
+ class MappingTransformPerson < Lutaml::Model::Serializable
21
+ attribute :name, :string
22
+ attribute :email, :string
23
+ attribute :tags, :string, collection: true
24
+
25
+ json do
26
+ map "fullName", to: :name, transform: {
27
+ export: ->(value) { "Dr. #{value}" },
28
+ }
29
+ map "emailAddress", to: :email, transform: {
30
+ import: ->(value) { value.gsub("at", "@") },
31
+ }
32
+ map "labels", to: :tags, transform: {
33
+ export: ->(value) { value.join("-|-") },
34
+ import: ->(value) { value.split("|") },
35
+ }
36
+ end
37
+
38
+ xml do
39
+ root "person"
40
+ map_element "full-name", to: :name, transform: {
41
+ export: ->(value) { "Dr. #{value}" },
42
+ }
43
+ map_element "email-address", to: :email, transform: {
44
+ import: ->(value) { value.gsub("at", "@") },
45
+ }
46
+ map_element "labels", to: :tags, transform: {
47
+ export: ->(value) { value.join("-|-") },
48
+ import: ->(value) { value.split("|") },
49
+ }
50
+ end
51
+ end
52
+
53
+ # Class with both attribute and mapping transformations
54
+ class CombinedTransformPerson < Lutaml::Model::Serializable
55
+ attribute :name, :string, transform: {
56
+ export: ->(value) { value.to_s.capitalize },
57
+ import: ->(value) { value.to_s.downcase },
58
+ }
59
+ attribute :email, :string, transform: {
60
+ export: lambda(&:downcase),
61
+ import: lambda(&:downcase),
62
+ }
63
+ attribute :tags, :string, collection: true, transform: {
64
+ export: ->(value) { value.map(&:upcase) },
65
+ import: ->(value) { value.map { |v| "#{v}-1" } },
66
+ }
67
+
68
+ json do
69
+ map "fullName", to: :name, transform: {
70
+ export: ->(value) { "Prof. #{value}" },
71
+ import: ->(value) { value.gsub("Prof. ", "") },
72
+ }
73
+ map "contactEmail", to: :email, transform: {
74
+ export: ->(value) { "contact+#{value}" },
75
+ import: ->(value) { value.gsub("contact+", "") },
76
+ }
77
+ map "skills", to: :tags
78
+ end
79
+
80
+ xml do
81
+ root "person"
82
+ map_element "full-name", to: :name, transform: {
83
+ export: ->(value) { "Prof. #{value}" },
84
+ import: ->(value) { value.gsub("Prof. ", "") },
85
+ }
86
+ map_element "contact-email", to: :email, transform: {
87
+ export: ->(value) { "contact+#{value}" },
88
+ import: ->(value) { value.gsub("contact+", "") },
89
+ }
90
+ map_element "skills", to: :tags
91
+ end
92
+ end
93
+ end
94
+
95
+ describe "Attribute-only transformations" do
96
+ let(:attribute_person) do
97
+ TransformationSpec::AttributeTransformPerson.new(
98
+ name: "john",
99
+ email: "smith",
100
+ tags: ["ruby", "rails"],
101
+ )
102
+ end
103
+
104
+ let(:expected_xml) do
105
+ <<~XML
106
+ <AttributeTransformPerson>
107
+ <name>JOHN</name>
108
+ <email>smith</email>
109
+ <tags>RUBY</tags>
110
+ <tags>RAILS</tags>
111
+ </AttributeTransformPerson>
112
+ XML
113
+ end
114
+
115
+ it "applies attribute transformations during serialization" do
116
+ parsed_json = JSON.parse(attribute_person.to_json)
117
+ expect(parsed_json["name"]).to eq("JOHN")
118
+ expect(parsed_json["email"]).to eq("smith")
119
+ expect(parsed_json["tags"]).to eq(["RUBY", "RAILS"])
120
+ end
121
+
122
+ it "applies attribute transformations during deserialization" do
123
+ json = {
124
+ "name" => "jane",
125
+ "email" => "doe",
126
+ "tags" => ["python", "django"],
127
+ }.to_json
128
+
129
+ parsed = TransformationSpec::AttributeTransformPerson.from_json(json)
130
+ expect(parsed.name).to eq("jane")
131
+ expect(parsed.email).to eq("doe@example.com")
132
+ expect(parsed.tags).to eq(["python-1", "django-1"])
133
+ end
134
+
135
+ it "applies attribute transformations during XML serialization" do
136
+ xml = attribute_person.to_xml
137
+ expect(xml).to be_equivalent_to(expected_xml)
138
+ end
139
+ end
140
+
141
+ describe "Mapping-only transformations" do
142
+ let(:mapping_person) do
143
+ TransformationSpec::MappingTransformPerson.new(
144
+ name: "alice",
145
+ email: "aliceattest.com",
146
+ tags: ["developer", "architect"],
147
+ )
148
+ end
149
+
150
+ let(:expected_xml) do
151
+ <<~XML
152
+ <person>
153
+ <full-name>Dr. alice</full-name>
154
+ <email-address>aliceattest.com</email-address>
155
+ <labels>developer-|-architect</labels>
156
+ </person>
157
+ XML
158
+ end
159
+
160
+ it "applies mapping transformations during JSON serialization" do
161
+ json = mapping_person.to_json
162
+ parsed = JSON.parse(json)
163
+ expect(parsed["fullName"]).to eq("Dr. alice")
164
+ expect(parsed["emailAddress"]).to eq("aliceattest.com")
165
+ expect(parsed["labels"]).to eq("developer-|-architect")
166
+ end
167
+
168
+ it "applies mapping transformations during JSON deserialization" do
169
+ json = {
170
+ "fullName" => "Dr. bob",
171
+ "emailAddress" => "bobattest.com",
172
+ "labels" => "senior|lead",
173
+ }.to_json
174
+
175
+ parsed = TransformationSpec::MappingTransformPerson.from_json(json)
176
+ expect(parsed.name).to eq("Dr. bob")
177
+ expect(parsed.email).to eq("bob@test.com")
178
+ expect(parsed.tags).to eq(["senior", "lead"])
179
+ end
180
+
181
+ it "applies mapping transformations during XML serialization" do
182
+ xml = mapping_person.to_xml
183
+ expect(xml).to be_equivalent_to(expected_xml)
184
+ end
185
+ end
186
+
187
+ describe "Combined transformations" do
188
+ let(:combined_person) do
189
+ TransformationSpec::CombinedTransformPerson.new(
190
+ name: "carol",
191
+ email: "CAROL@TEST.COM",
192
+ tags: ["manager", "agile"],
193
+ )
194
+ end
195
+
196
+ let(:expected_xml) do
197
+ <<~XML
198
+ <person>
199
+ <full-name>Prof. carol</full-name>
200
+ <contact-email>contact+CAROL@TEST.COM</contact-email>
201
+ <skills>MANAGER-1</skills>
202
+ <skills>AGILE-1</skills>
203
+ </person>
204
+ XML
205
+ end
206
+
207
+ it "applies both transformations with correct precedence in JSON" do
208
+ json = combined_person.to_json
209
+ parsed = JSON.parse(json)
210
+
211
+ expect(parsed["fullName"]).to eq("Prof. carol")
212
+ expect(parsed["contactEmail"]).to eq("contact+CAROL@TEST.COM")
213
+ end
214
+
215
+ it "handles round-trip transformations across formats" do
216
+ # JSON -> Key-Value -> JSON cycle
217
+ json = combined_person.to_json
218
+ parsed_json = TransformationSpec::CombinedTransformPerson.from_json(json)
219
+ expect(parsed_json.name).to eq("carol")
220
+ expect(parsed_json.email).to eq("CAROL@TEST.COM")
221
+ expect(parsed_json.tags).to eq(["MANAGER-1", "AGILE-1"])
222
+ end
223
+
224
+ it "applies both transformations with correct precedence in XML" do
225
+ xml = combined_person.to_xml
226
+ parsed_xml = TransformationSpec::CombinedTransformPerson.from_xml(xml)
227
+ expect(parsed_xml.to_xml).to be_equivalent_to(expected_xml)
228
+ end
229
+ end
230
+ end
@@ -2,16 +2,47 @@
2
2
  require "spec_helper"
3
3
  require "bigdecimal"
4
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
5
+ class CustomSerializationType < Lutaml::Model::Type::Value
6
+ def self.from_xml(_xml_string)
7
+ "from_xml_overrided"
8
+ end
9
+
10
+ def self.from_json(_value)
11
+ "from_json_overrided"
12
+ end
13
+
14
+ def self.serialize(_value)
15
+ "serialize_overrided"
16
+ end
17
+
18
+ def to_xml
19
+ "to_xml_overrided"
20
+ end
21
+
22
+ def to_json(*_args)
23
+ "to_json_overrided"
9
24
  end
10
25
  end
11
26
 
12
- class InvalidType
13
- def self.cast(value)
14
- value
27
+ class SampleModel < Lutaml::Model::Serializable
28
+ attribute :custom_type, CustomSerializationType
29
+ xml do
30
+ root "sample"
31
+ map_element "custom_type", to: :custom_type
32
+ end
33
+ json do
34
+ map_element "custom_type", to: :custom_type
35
+ end
36
+ end
37
+
38
+ class SampleModelAttribute < Lutaml::Model::Serializable
39
+ attribute :custom_type, CustomSerializationType
40
+ xml do
41
+ root "sample"
42
+ map_attribute "custom_type", to: :custom_type
43
+ end
44
+ json do
45
+ map_element "custom_type", to: :custom_type
15
46
  end
16
47
  end
17
48
 
@@ -19,6 +50,17 @@ RSpec.describe Lutaml::Model::Type do
19
50
  describe "Type System" do
20
51
  describe ".register and .lookup" do
21
52
  context "with valid types" do
53
+ before do
54
+ # Test class for type registration scenarios
55
+ custom_type = Class.new(Lutaml::Model::Type::Value) do
56
+ def self.cast(value)
57
+ value.to_s.upcase
58
+ end
59
+ end
60
+
61
+ stub_const("CustomType", custom_type)
62
+ end
63
+
22
64
  it "registers and looks up a custom type" do
23
65
  described_class.register(:custom, CustomType)
24
66
  expect(described_class.lookup(:custom)).to eq(CustomType)
@@ -33,6 +75,16 @@ RSpec.describe Lutaml::Model::Type do
33
75
  end
34
76
 
35
77
  context "with invalid types" do
78
+ before do
79
+ invalid_type = Class.new do
80
+ def self.cast(value)
81
+ value
82
+ end
83
+ end
84
+
85
+ stub_const("InvalidType", invalid_type)
86
+ end
87
+
36
88
  it "raises TypeError when registering non-Type::Value class" do
37
89
  expect do
38
90
  described_class.register(:invalid,
@@ -159,31 +211,35 @@ RSpec.describe Lutaml::Model::Type do
159
211
  end
160
212
 
161
213
  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
214
+ before do
215
+ type_test_model = Class.new(Lutaml::Model::Serializable) do
216
+ attribute :string_symbol, :string
217
+ attribute :string_class, Lutaml::Model::Type::String
218
+ attribute :integer_value, :integer
219
+ attribute :float_value, :float
220
+ attribute :date_value, :date
221
+ attribute :time_value, :time
222
+ attribute :time_without_date_value, :time_without_date
223
+ attribute :date_time_value, :date_time
224
+ attribute :boolean_value, :boolean
225
+ attribute :hash_value, :hash
226
+
227
+ xml do
228
+ root "test"
229
+ map_element "string_symbol", to: :string_symbol
230
+ map_element "string_class", to: :string_class
231
+ map_element "integer", to: :integer_value
232
+ map_element "float", to: :float_value
233
+ map_element "date", to: :date_value
234
+ map_element "time", to: :time_value
235
+ map_element "time_without_date", to: :time_without_date_value
236
+ map_element "date_time", to: :date_time_value
237
+ map_element "boolean", to: :boolean_value
238
+ map_element "hash", to: :hash_value
239
+ end
186
240
  end
241
+
242
+ stub_const("TypeTestModel", type_test_model)
187
243
  end
188
244
 
189
245
  let(:test_instance) do
@@ -272,5 +328,56 @@ RSpec.describe Lutaml::Model::Type do
272
328
  expect(deserialized.hash_value).to eq({ "key" => "value" })
273
329
  end
274
330
  end
331
+
332
+ describe "Serialization Of Custom Type" do
333
+ let(:xml) do
334
+ <<~XML
335
+ <sample>
336
+ <custom_type>test_string</custom_type>
337
+ </sample>
338
+ XML
339
+ end
340
+
341
+ let(:xml_attribute) do
342
+ <<~XML
343
+ <sample custom_type="test_string"/>
344
+ XML
345
+ end
346
+
347
+ let(:sample_instance) { SampleModel.from_xml(xml) }
348
+ let(:sample_instance_attribute) do
349
+ SampleModelAttribute.from_xml(xml_attribute)
350
+ end
351
+
352
+ it "correctly serializes to XML" do
353
+ expected_xml = <<~XML
354
+ <sample>
355
+ <custom_type>to_xml_overrided</custom_type>
356
+ </sample>
357
+ XML
358
+ expect(sample_instance.to_xml).to be_equivalent_to(expected_xml)
359
+ end
360
+
361
+ it "correctly serializes to XML attribute" do
362
+ expected_xml = <<~XML
363
+ <sample custom_type="to_xml_overrided"/>
364
+ XML
365
+ expect(sample_instance_attribute.to_xml).to be_equivalent_to(expected_xml)
366
+ end
367
+
368
+ it "correctly serializes to JSON" do
369
+ expect(sample_instance.to_json).to eq('{"custom_type":"to_json_overrided"}')
370
+ end
371
+
372
+ it "correctly deserializes from XML" do
373
+ expect(sample_instance.custom_type).to eq("from_xml_overrided")
374
+ end
375
+
376
+ it "correctly deserializes from JSON" do
377
+ json_sample_instance = SampleModel.from_json('{"custom_type":"test_string"}')
378
+ json_sample_instance.to_json
379
+ expect(json_sample_instance.custom_type).to eq("from_json_overrided")
380
+ end
381
+ end
275
382
  end
276
383
  end
@@ -54,7 +54,19 @@ RSpec.describe Lutaml::Model::Utils do
54
54
  }
55
55
  end
56
56
 
57
+ let(:original_array) do
58
+ [
59
+ "one", [
60
+ "one_one", [
61
+ "one_one1", "one_one2"
62
+ ],
63
+ "one_two"
64
+ ]
65
+ ]
66
+ end
67
+
57
68
  let(:duplicate_hash) { utils.deep_dup(original_hash) }
69
+ let(:duplicate_array) { utils.deep_dup(original_array) }
58
70
 
59
71
  it "creates deep duplicate of hash" do
60
72
  expect(original_hash).to eq(duplicate_hash)
@@ -75,5 +87,25 @@ RSpec.describe Lutaml::Model::Utils do
75
87
  expect(original_hash[:one][:one_two]).to eq(duplicate_hash[:one][:one_two])
76
88
  expect(original_hash[:one][:one_two].object_id).not_to eq(duplicate_hash[:one][:one_two].object_id)
77
89
  end
90
+
91
+ it "creates deep duplicate of array" do
92
+ expect(original_array).to eq(duplicate_array)
93
+ expect(original_array.object_id).not_to eq(duplicate_array.object_id)
94
+
95
+ expect(original_array[0]).to eq(duplicate_array[0])
96
+ expect(original_array[0].object_id).not_to eq(duplicate_array[0].object_id)
97
+
98
+ expect(original_array[1][0]).to eq(duplicate_array[1][0])
99
+ expect(original_array[1][0].object_id).not_to eq(duplicate_array[1][0].object_id)
100
+
101
+ expect(original_array[1][1][0]).to eq(duplicate_array[1][1][0])
102
+ expect(original_array[1][1][0].object_id).not_to eq(duplicate_array[1][1][0].object_id)
103
+
104
+ expect(original_array[1][1][1]).to eq(duplicate_array[1][1][1])
105
+ expect(original_array[1][1][1].object_id).not_to eq(duplicate_array[1][1][1].object_id)
106
+
107
+ expect(original_array[1][2]).to eq(duplicate_array[1][2])
108
+ expect(original_array[1][2].object_id).not_to eq(duplicate_array[1][2].object_id)
109
+ end
78
110
  end
79
111
  end