lutaml-model 0.3.24 → 0.3.25
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +35 -16
- data/README.adoc +274 -28
- data/lib/lutaml/model/attribute.rb +18 -8
- data/lib/lutaml/model/error/type_error.rb +9 -0
- data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
- data/lib/lutaml/model/error/validation_error.rb +0 -1
- data/lib/lutaml/model/error.rb +2 -0
- data/lib/lutaml/model/serialize.rb +6 -1
- data/lib/lutaml/model/type/boolean.rb +38 -0
- data/lib/lutaml/model/type/date.rb +35 -0
- data/lib/lutaml/model/type/date_time.rb +32 -4
- data/lib/lutaml/model/type/decimal.rb +42 -0
- data/lib/lutaml/model/type/float.rb +37 -0
- data/lib/lutaml/model/type/hash.rb +62 -0
- data/lib/lutaml/model/type/integer.rb +41 -0
- data/lib/lutaml/model/type/string.rb +49 -0
- data/lib/lutaml/model/type/time.rb +49 -0
- data/lib/lutaml/model/type/time_without_date.rb +37 -5
- data/lib/lutaml/model/type/value.rb +52 -0
- data/lib/lutaml/model/type.rb +50 -114
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +5 -2
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +2 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +0 -2
- data/lutaml-model.gemspec +1 -1
- data/spec/address_spec.rb +170 -0
- data/spec/fixtures/address.rb +33 -0
- data/spec/fixtures/person.rb +73 -0
- data/spec/fixtures/sample_model.rb +40 -0
- data/spec/fixtures/vase.rb +38 -0
- data/spec/fixtures/xml/special_char.xml +13 -0
- data/spec/lutaml/model/attribute_spec.rb +112 -0
- data/spec/lutaml/model/collection_spec.rb +299 -0
- data/spec/lutaml/model/comparable_model_spec.rb +106 -0
- data/spec/lutaml/model/custom_model_spec.rb +410 -0
- data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
- data/spec/lutaml/model/defaults_spec.rb +221 -0
- data/spec/lutaml/model/delegation_spec.rb +340 -0
- data/spec/lutaml/model/inheritance_spec.rb +92 -0
- data/spec/lutaml/model/json_adapter_spec.rb +37 -0
- data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
- data/spec/lutaml/model/map_content_spec.rb +118 -0
- data/spec/lutaml/model/mixed_content_spec.rb +625 -0
- data/spec/lutaml/model/namespace_spec.rb +57 -0
- data/spec/lutaml/model/ordered_content_spec.rb +83 -0
- data/spec/lutaml/model/render_nil_spec.rb +138 -0
- data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
- data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
- data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
- data/spec/lutaml/model/serializable_spec.rb +297 -0
- data/spec/lutaml/model/serializable_validation_spec.rb +85 -0
- data/spec/lutaml/model/simple_model_spec.rb +314 -0
- data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
- data/spec/lutaml/model/type/boolean_spec.rb +54 -0
- data/spec/lutaml/model/type/date_spec.rb +118 -0
- data/spec/lutaml/model/type/date_time_spec.rb +127 -0
- data/spec/lutaml/model/type/decimal_spec.rb +125 -0
- data/spec/lutaml/model/type/float_spec.rb +191 -0
- data/spec/lutaml/model/type/hash_spec.rb +63 -0
- data/spec/lutaml/model/type/integer_spec.rb +145 -0
- data/spec/lutaml/model/type/string_spec.rb +150 -0
- data/spec/lutaml/model/type/time_spec.rb +142 -0
- data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
- data/spec/lutaml/model/type_spec.rb +276 -0
- data/spec/lutaml/model/utils_spec.rb +79 -0
- data/spec/lutaml/model/validation_spec.rb +83 -0
- data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
- data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
- data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
- data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
- data/spec/lutaml/model_spec.rb +1 -0
- data/spec/person_spec.rb +161 -0
- data/spec/spec_helper.rb +33 -0
- metadata +66 -2
@@ -0,0 +1,39 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "lutaml/model/toml_adapter/toml_rb_adapter"
|
3
|
+
require "lutaml/model/toml_adapter/tomlib_adapter"
|
4
|
+
require_relative "../../fixtures/sample_model"
|
5
|
+
|
6
|
+
RSpec.shared_examples "a TOML adapter" do |adapter_class|
|
7
|
+
let(:attributes) { { name: "John Doe", age: 30 } }
|
8
|
+
let(:model) { SampleModel.new(attributes) }
|
9
|
+
|
10
|
+
let(:expected_toml) do
|
11
|
+
if adapter_class == Lutaml::Model::TomlAdapter::TomlRbAdapter
|
12
|
+
TomlRB.dump(attributes)
|
13
|
+
elsif adapter_class == Lutaml::Model::TomlAdapter::TomlibAdapter
|
14
|
+
Tomlib.dump(attributes)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "serializes to TOML" do
|
19
|
+
toml = adapter_class.new(attributes).to_toml
|
20
|
+
|
21
|
+
expect(toml).to eq(expected_toml)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "deserializes from TOML" do
|
25
|
+
doc = adapter_class.parse(expected_toml)
|
26
|
+
new_model = SampleModel.new(doc.to_h)
|
27
|
+
|
28
|
+
expect(new_model.name).to eq("John Doe")
|
29
|
+
expect(new_model.age).to eq(30)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
RSpec.describe Lutaml::Model::TomlAdapter::TomlRbAdapter do
|
34
|
+
it_behaves_like "a TOML adapter", described_class
|
35
|
+
end
|
36
|
+
|
37
|
+
RSpec.describe Lutaml::Model::TomlAdapter::TomlibAdapter do
|
38
|
+
it_behaves_like "a TOML adapter", described_class
|
39
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Lutaml::Model::Type::Boolean do
|
4
|
+
describe ".cast" do
|
5
|
+
let(:truthy_values) { [true, "true", "t", "yes", "y", "1"] }
|
6
|
+
let(:falsey_values) { [false, "false", "f", "no", "n", "0"] }
|
7
|
+
|
8
|
+
it "returns nil for nil input" do
|
9
|
+
expect(described_class.cast(nil)).to be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
context "with truthy values" do
|
13
|
+
it "casts to true" do
|
14
|
+
truthy_values.each do |value|
|
15
|
+
expect(described_class.cast(value)).to be true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with falsey values" do
|
21
|
+
it "casts to false" do
|
22
|
+
falsey_values.each do |value|
|
23
|
+
expect(described_class.cast(value)).to be false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with other values" do
|
29
|
+
it "returns the original value" do
|
30
|
+
value = "other"
|
31
|
+
expect(described_class.cast(value)).to eq value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe ".serialize" do
|
37
|
+
it "returns nil for nil input" do
|
38
|
+
expect(described_class.serialize(nil)).to be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns true for truthy input" do
|
42
|
+
expect(described_class.serialize(true)).to be true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns false for falsey input" do
|
46
|
+
expect(described_class.serialize(false)).to be false
|
47
|
+
end
|
48
|
+
|
49
|
+
it "preserves input boolean values" do
|
50
|
+
expect(described_class.serialize(false)).to be false
|
51
|
+
expect(described_class.serialize(true)).to be true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Lutaml::Model::Type::Date do
|
4
|
+
describe ".cast" do
|
5
|
+
subject(:cast) { described_class.cast(value) }
|
6
|
+
|
7
|
+
context "with nil value" do
|
8
|
+
let(:value) { nil }
|
9
|
+
|
10
|
+
it { is_expected.to be_nil }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with valid Date string" do
|
14
|
+
let(:value) { "2024-01-01" }
|
15
|
+
|
16
|
+
it { is_expected.to eq(Date.new(2024, 1, 1)) }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with Date object" do
|
20
|
+
let(:value) { Date.new(2024, 1, 1) }
|
21
|
+
|
22
|
+
it { is_expected.to eq(value) }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with DateTime object" do
|
26
|
+
let(:value) { DateTime.new(2024, 1, 1, 12, 0, 0) }
|
27
|
+
|
28
|
+
it { is_expected.to eq(Date.new(2024, 1, 1)) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with Time object" do
|
32
|
+
let(:value) { Time.new(2024, 1, 1, 12, 0, 0) }
|
33
|
+
|
34
|
+
it { is_expected.to eq(Date.new(2024, 1, 1)) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with invalid date string" do
|
38
|
+
let(:value) { "not a date" }
|
39
|
+
|
40
|
+
it { is_expected.to be_nil }
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with invalid month" do
|
44
|
+
let(:value) { "2024-13-01" }
|
45
|
+
|
46
|
+
it { is_expected.to be_nil }
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with invalid day" do
|
50
|
+
let(:value) { "2024-04-31" }
|
51
|
+
|
52
|
+
it { is_expected.to be_nil }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with different date formats" do
|
56
|
+
it "parses ISO 8601" do
|
57
|
+
expect(described_class.cast("2024-01-01")).to eq(Date.new(2024, 1, 1))
|
58
|
+
end
|
59
|
+
|
60
|
+
it "parses RFC 3339" do
|
61
|
+
expect(described_class.cast("2024-01-01T12:00:00Z")).to eq(Date.new(
|
62
|
+
2024, 1, 1
|
63
|
+
))
|
64
|
+
end
|
65
|
+
|
66
|
+
it "parses common formats" do
|
67
|
+
expect(described_class.cast("01/01/2024")).to eq(Date.new(2024, 1, 1))
|
68
|
+
expect(described_class.cast("Jan 1, 2024")).to eq(Date.new(2024, 1, 1))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with leap year dates" do
|
73
|
+
it "handles February 29 in leap years" do
|
74
|
+
expect(described_class.cast("2024-02-29")).to eq(Date.new(2024, 2, 29))
|
75
|
+
end
|
76
|
+
|
77
|
+
it "rejects February 29 in non-leap years" do
|
78
|
+
expect(described_class.cast("2023-02-29")).to be_nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe ".serialize" do
|
84
|
+
subject(:serialize) { described_class.serialize(value) }
|
85
|
+
|
86
|
+
context "with nil value" do
|
87
|
+
let(:value) { nil }
|
88
|
+
|
89
|
+
it { is_expected.to be_nil }
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with Date object" do
|
93
|
+
let(:value) { Date.new(2024, 1, 1) }
|
94
|
+
|
95
|
+
it { is_expected.to eq("2024-01-01") }
|
96
|
+
end
|
97
|
+
|
98
|
+
context "with single-digit month and day" do
|
99
|
+
let(:value) { Date.new(2024, 1, 1) }
|
100
|
+
|
101
|
+
it "zero-pads month and day" do
|
102
|
+
expect(serialize).to eq("2024-01-01")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "with double-digit month and day" do
|
107
|
+
let(:value) { Date.new(2024, 12, 31) }
|
108
|
+
|
109
|
+
it { is_expected.to eq("2024-12-31") }
|
110
|
+
end
|
111
|
+
|
112
|
+
context "with leap year date" do
|
113
|
+
let(:value) { Date.new(2024, 2, 29) }
|
114
|
+
|
115
|
+
it { is_expected.to eq("2024-02-29") }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Lutaml::Model::Type::DateTime do
|
4
|
+
describe ".cast" do
|
5
|
+
subject(:cast) { described_class.cast(value) }
|
6
|
+
|
7
|
+
context "with nil value" do
|
8
|
+
let(:value) { nil }
|
9
|
+
|
10
|
+
it { is_expected.to be_nil }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with valid DateTime string" do
|
14
|
+
let(:value) { "2024-01-01T12:00:00Z" }
|
15
|
+
|
16
|
+
it "parses with UTC offset" do
|
17
|
+
expect(cast.offset).to eq(0)
|
18
|
+
expect(cast.zone).to eq("+00:00")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "with positive timezone offset" do
|
23
|
+
let(:value) { "2024-01-01T12:00:00+08:00" }
|
24
|
+
|
25
|
+
it "retains the positive offset" do
|
26
|
+
expect(cast.offset).to eq(Rational(8, 24))
|
27
|
+
expect(cast.zone).to eq("+08:00")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with negative timezone offset" do
|
32
|
+
let(:value) { "2024-01-01T12:00:00-05:00" }
|
33
|
+
|
34
|
+
it "retains the negative offset" do
|
35
|
+
expect(cast.offset).to eq(Rational(-5, 24))
|
36
|
+
expect(cast.zone).to eq("-05:00")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with fractional offset" do
|
41
|
+
let(:value) { "2024-01-01T12:00:00+05:30" }
|
42
|
+
|
43
|
+
it "retains the fractional offset" do
|
44
|
+
expect(cast.offset).to eq(Rational(5.5, 24))
|
45
|
+
expect(cast.zone).to eq("+05:30")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with Time object with offset" do
|
50
|
+
let(:value) { Time.new(2024, 1, 1, 12, 0, 0, "+08:00") }
|
51
|
+
|
52
|
+
it "preserves the offset from Time object" do
|
53
|
+
expect(cast.offset).to eq(Rational(8, 24))
|
54
|
+
expect(cast.zone).to eq("+08:00")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with DateTime object with offset" do
|
59
|
+
let(:value) { DateTime.new(2024, 1, 1, 12, 0, 0, "+08:00") }
|
60
|
+
|
61
|
+
it "preserves the offset from DateTime object" do
|
62
|
+
expect(cast.offset).to eq(Rational(8, 24))
|
63
|
+
expect(cast.zone).to eq("+08:00")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe ".serialize" do
|
69
|
+
subject(:serialize) { described_class.serialize(value) }
|
70
|
+
|
71
|
+
context "with nil value" do
|
72
|
+
let(:value) { nil }
|
73
|
+
|
74
|
+
it { is_expected.to be_nil }
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with UTC DateTime" do
|
78
|
+
let(:value) { DateTime.new(2024, 1, 1, 12, 0, 0) }
|
79
|
+
|
80
|
+
it "serializes with +00:00 offset" do
|
81
|
+
expect(serialize).to eq("2024-01-01T12:00:00+00:00")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with positive offset" do
|
86
|
+
let(:value) { DateTime.new(2024, 1, 1, 12, 0, 0, "+08:00") }
|
87
|
+
|
88
|
+
it "retains positive offset in serialized form" do
|
89
|
+
expect(serialize).to eq("2024-01-01T12:00:00+08:00")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "with negative offset" do
|
94
|
+
let(:value) { DateTime.new(2024, 1, 1, 12, 0, 0, "-05:00") }
|
95
|
+
|
96
|
+
it "retains negative offset in serialized form" do
|
97
|
+
expect(serialize).to eq("2024-01-01T12:00:00-05:00")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "with fractional offset" do
|
102
|
+
let(:value) { DateTime.new(2024, 1, 1, 12, 0, 0, "+05:30") }
|
103
|
+
|
104
|
+
it "retains fractional offset in serialized form" do
|
105
|
+
expect(serialize).to eq("2024-01-01T12:00:00+05:30")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "with fractional seconds" do
|
110
|
+
let(:value) { DateTime.new(2024, 1, 1, 12, 0, 0.5, "+08:00") }
|
111
|
+
|
112
|
+
xit "retains both fractional seconds and offset" do
|
113
|
+
expect(serialize).to eq("2024-01-01T12:00:00.500+08:00")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "with microsecond precision" do
|
118
|
+
let(:value) do
|
119
|
+
DateTime.new(2024, 1, 1, 12, 0, Rational(123456, 1000000), "+08:00")
|
120
|
+
end
|
121
|
+
|
122
|
+
xit "retains microsecond precision and offset" do
|
123
|
+
expect(serialize).to eq("2024-01-01T12:00:00.123456+08:00")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "bigdecimal"
|
3
|
+
|
4
|
+
RSpec.describe Lutaml::Model::Type do
|
5
|
+
describe "Decimal type" do
|
6
|
+
let(:decimal_class) { Lutaml::Model::Type::Decimal }
|
7
|
+
|
8
|
+
context "when bigdecimal is loaded" do
|
9
|
+
before do
|
10
|
+
# Ensure BigDecimal is loaded
|
11
|
+
require "bigdecimal"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "serializes into Lutaml::Model::Type::Decimal" do
|
15
|
+
value = BigDecimal("123.45")
|
16
|
+
serialized = decimal_class.serialize(value)
|
17
|
+
expect(serialized).to eq("123.45")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "deserializes into Ruby BigDecimal" do
|
21
|
+
value = "123.45"
|
22
|
+
deserialized = decimal_class.cast(value)
|
23
|
+
expect(deserialized).to be_a(BigDecimal)
|
24
|
+
expect(deserialized).to eq(BigDecimal("123.45"))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when bigdecimal is not loaded" do
|
29
|
+
before do
|
30
|
+
hide_const("BigDecimal")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "raises TypeNotEnabledError when serializing" do
|
34
|
+
expect do
|
35
|
+
decimal_class.serialize(123.45)
|
36
|
+
end.to raise_error(Lutaml::Model::TypeNotEnabledError, /Decimal/)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "raises TypeNotEnabledError when deserializing" do
|
40
|
+
expect do
|
41
|
+
decimal_class.cast("123.45")
|
42
|
+
end.to raise_error(Lutaml::Model::TypeNotEnabledError, /Decimal/)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".cast" do
|
48
|
+
context "with BigDecimal available" do
|
49
|
+
before do
|
50
|
+
# Ensure BigDecimal is loaded
|
51
|
+
require "bigdecimal"
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:decimal_class) { Lutaml::Model::Type::Decimal }
|
55
|
+
|
56
|
+
it "returns nil for nil input" do
|
57
|
+
expect(decimal_class.cast(nil)).to be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it "casts numeric string to BigDecimal" do
|
61
|
+
expect(decimal_class.cast("123.45")).to eq BigDecimal("123.45")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "casts integer to BigDecimal" do
|
65
|
+
expect(decimal_class.cast(123)).to eq BigDecimal("123")
|
66
|
+
end
|
67
|
+
|
68
|
+
it "casts float to BigDecimal" do
|
69
|
+
expect(decimal_class.cast(123.45)).to eq BigDecimal("123.45")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "casts BigDecimal to BigDecimal" do
|
73
|
+
value = BigDecimal("123.45")
|
74
|
+
expect(decimal_class.cast(value)).to eq value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "with BigDecimal not available", :skip_before do
|
79
|
+
before do
|
80
|
+
hide_const("BigDecimal")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "raises TypeNotEnabledError" do
|
84
|
+
expect { Lutaml::Model::Type::Decimal.cast("123.45") }.to raise_error(
|
85
|
+
Lutaml::Model::TypeNotEnabledError,
|
86
|
+
/Decimal/,
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe ".serialize" do
|
93
|
+
context "with BigDecimal available" do
|
94
|
+
let(:decimal_class) { Lutaml::Model::Type::Decimal }
|
95
|
+
|
96
|
+
before do
|
97
|
+
# Ensure BigDecimal is loaded
|
98
|
+
require "bigdecimal"
|
99
|
+
end
|
100
|
+
|
101
|
+
it "returns nil for nil input" do
|
102
|
+
expect(decimal_class.serialize(nil)).to be_nil
|
103
|
+
end
|
104
|
+
|
105
|
+
it "serializes BigDecimal to string" do
|
106
|
+
expect(decimal_class.serialize(BigDecimal("123.45"))).to eq "123.45"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "with BigDecimal not available", :skip_before do
|
111
|
+
before do
|
112
|
+
hide_const("BigDecimal")
|
113
|
+
end
|
114
|
+
|
115
|
+
it "raises TypeNotEnabledError" do
|
116
|
+
expect do
|
117
|
+
Lutaml::Model::Type::Decimal.serialize("123.45")
|
118
|
+
end.to raise_error(
|
119
|
+
Lutaml::Model::TypeNotEnabledError,
|
120
|
+
/Decimal/,
|
121
|
+
)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Lutaml::Model::Type::Float do
|
4
|
+
describe ".cast" do
|
5
|
+
subject(:cast) { described_class.cast(value) }
|
6
|
+
|
7
|
+
context "with nil value" do
|
8
|
+
let(:value) { nil }
|
9
|
+
|
10
|
+
it { is_expected.to be_nil }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with float" do
|
14
|
+
let(:value) { 123.45 }
|
15
|
+
|
16
|
+
it { is_expected.to eq(123.45) }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with integer" do
|
20
|
+
let(:value) { 123 }
|
21
|
+
|
22
|
+
it { is_expected.to eq(123.0) }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with float string" do
|
26
|
+
let(:value) { "123.45" }
|
27
|
+
|
28
|
+
it { is_expected.to eq(123.45) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with integer string" do
|
32
|
+
let(:value) { "123" }
|
33
|
+
|
34
|
+
it { is_expected.to eq(123.0) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with negative float" do
|
38
|
+
let(:value) { -123.45 }
|
39
|
+
|
40
|
+
it { is_expected.to eq(-123.45) }
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with negative float string" do
|
44
|
+
let(:value) { "-123.45" }
|
45
|
+
|
46
|
+
it { is_expected.to eq(-123.45) }
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with exponential notation" do
|
50
|
+
let(:value) { "1.2345e2" }
|
51
|
+
|
52
|
+
it { is_expected.to eq(123.45) }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with negative exponential notation" do
|
56
|
+
let(:value) { "-1.2345e2" }
|
57
|
+
|
58
|
+
it { is_expected.to eq(-123.45) }
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with very small exponential notation" do
|
62
|
+
let(:value) { "1.2345e-2" }
|
63
|
+
|
64
|
+
it { is_expected.to eq(0.012345) }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with string containing leading zeros" do
|
68
|
+
let(:value) { "000123.45" }
|
69
|
+
|
70
|
+
it { is_expected.to eq(123.45) }
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with trailing zeros" do
|
74
|
+
let(:value) { "123.4500" }
|
75
|
+
|
76
|
+
it { is_expected.to eq(123.45) }
|
77
|
+
end
|
78
|
+
|
79
|
+
context "with plus sign" do
|
80
|
+
let(:value) { "+123.45" }
|
81
|
+
|
82
|
+
it { is_expected.to eq(123.45) }
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with whitespace" do
|
86
|
+
let(:value) { " 123.45 " }
|
87
|
+
|
88
|
+
it { is_expected.to eq(123.45) }
|
89
|
+
end
|
90
|
+
|
91
|
+
context "with invalid string" do
|
92
|
+
let(:value) { "not a float" }
|
93
|
+
|
94
|
+
it { is_expected.to eq(0.0) }
|
95
|
+
end
|
96
|
+
|
97
|
+
context "with special float values" do
|
98
|
+
it "handles infinity" do
|
99
|
+
expect(described_class.cast(Float::INFINITY)).to eq(Float::INFINITY)
|
100
|
+
expect(described_class.cast(-Float::INFINITY)).to eq(-Float::INFINITY)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "handles NaN" do
|
104
|
+
expect(described_class.cast(Float::NAN)).to be_nan
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "with very large numbers" do
|
109
|
+
let(:value) { "1.23456789e100" }
|
110
|
+
|
111
|
+
it { is_expected.to eq(1.23456789e100) }
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with very small numbers" do
|
115
|
+
let(:value) { "1.23456789e-100" }
|
116
|
+
|
117
|
+
it { is_expected.to eq(1.23456789e-100) }
|
118
|
+
end
|
119
|
+
|
120
|
+
context "with precision edge cases" do
|
121
|
+
it "handles floating point precision" do
|
122
|
+
expect(described_class.cast(0.1 + 0.2)).to be_within(0.0000001).of(0.3)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe ".serialize" do
|
128
|
+
subject(:serialize) { described_class.serialize(value) }
|
129
|
+
|
130
|
+
context "with nil value" do
|
131
|
+
let(:value) { nil }
|
132
|
+
|
133
|
+
it { is_expected.to be_nil }
|
134
|
+
end
|
135
|
+
|
136
|
+
context "with positive float" do
|
137
|
+
let(:value) { 123.45 }
|
138
|
+
|
139
|
+
it { is_expected.to eq(123.45) }
|
140
|
+
end
|
141
|
+
|
142
|
+
context "with negative float" do
|
143
|
+
let(:value) { -123.45 }
|
144
|
+
|
145
|
+
it { is_expected.to eq(-123.45) }
|
146
|
+
end
|
147
|
+
|
148
|
+
context "with zero" do
|
149
|
+
let(:value) { 0.0 }
|
150
|
+
|
151
|
+
it { is_expected.to eq(0.0) }
|
152
|
+
end
|
153
|
+
|
154
|
+
context "with integer-like float" do
|
155
|
+
let(:value) { 123.0 }
|
156
|
+
|
157
|
+
it { is_expected.to eq(123.0) }
|
158
|
+
end
|
159
|
+
|
160
|
+
context "with very small number" do
|
161
|
+
let(:value) { 0.000000123 }
|
162
|
+
|
163
|
+
it { is_expected.to eq(0.000000123) }
|
164
|
+
end
|
165
|
+
|
166
|
+
context "with very large number" do
|
167
|
+
let(:value) { 1.23e100 }
|
168
|
+
|
169
|
+
it { is_expected.to eq(1.23e100) }
|
170
|
+
end
|
171
|
+
|
172
|
+
context "with special float values" do
|
173
|
+
it "serializes infinity" do
|
174
|
+
expect(described_class.serialize(Float::INFINITY)).to eq(Float::INFINITY)
|
175
|
+
expect(described_class.serialize(-Float::INFINITY)).to eq(-Float::INFINITY)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "serializes NaN" do
|
179
|
+
expect(described_class.serialize(Float::NAN)).to be_nan
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "with maximum precision" do
|
184
|
+
let(:value) { 123.456789123456789 }
|
185
|
+
|
186
|
+
it "maintains precision" do
|
187
|
+
expect(serialize).to eq(123.456789123456789)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|