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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +35 -16
  3. data/README.adoc +274 -28
  4. data/lib/lutaml/model/attribute.rb +18 -8
  5. data/lib/lutaml/model/error/type_error.rb +9 -0
  6. data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
  7. data/lib/lutaml/model/error/validation_error.rb +0 -1
  8. data/lib/lutaml/model/error.rb +2 -0
  9. data/lib/lutaml/model/serialize.rb +6 -1
  10. data/lib/lutaml/model/type/boolean.rb +38 -0
  11. data/lib/lutaml/model/type/date.rb +35 -0
  12. data/lib/lutaml/model/type/date_time.rb +32 -4
  13. data/lib/lutaml/model/type/decimal.rb +42 -0
  14. data/lib/lutaml/model/type/float.rb +37 -0
  15. data/lib/lutaml/model/type/hash.rb +62 -0
  16. data/lib/lutaml/model/type/integer.rb +41 -0
  17. data/lib/lutaml/model/type/string.rb +49 -0
  18. data/lib/lutaml/model/type/time.rb +49 -0
  19. data/lib/lutaml/model/type/time_without_date.rb +37 -5
  20. data/lib/lutaml/model/type/value.rb +52 -0
  21. data/lib/lutaml/model/type.rb +50 -114
  22. data/lib/lutaml/model/version.rb +1 -1
  23. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +5 -2
  24. data/lib/lutaml/model/xml_adapter/ox_adapter.rb +2 -1
  25. data/lib/lutaml/model/xml_adapter/xml_document.rb +0 -2
  26. data/lutaml-model.gemspec +1 -1
  27. data/spec/address_spec.rb +170 -0
  28. data/spec/fixtures/address.rb +33 -0
  29. data/spec/fixtures/person.rb +73 -0
  30. data/spec/fixtures/sample_model.rb +40 -0
  31. data/spec/fixtures/vase.rb +38 -0
  32. data/spec/fixtures/xml/special_char.xml +13 -0
  33. data/spec/lutaml/model/attribute_spec.rb +112 -0
  34. data/spec/lutaml/model/collection_spec.rb +299 -0
  35. data/spec/lutaml/model/comparable_model_spec.rb +106 -0
  36. data/spec/lutaml/model/custom_model_spec.rb +410 -0
  37. data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
  38. data/spec/lutaml/model/defaults_spec.rb +221 -0
  39. data/spec/lutaml/model/delegation_spec.rb +340 -0
  40. data/spec/lutaml/model/inheritance_spec.rb +92 -0
  41. data/spec/lutaml/model/json_adapter_spec.rb +37 -0
  42. data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
  43. data/spec/lutaml/model/map_content_spec.rb +118 -0
  44. data/spec/lutaml/model/mixed_content_spec.rb +625 -0
  45. data/spec/lutaml/model/namespace_spec.rb +57 -0
  46. data/spec/lutaml/model/ordered_content_spec.rb +83 -0
  47. data/spec/lutaml/model/render_nil_spec.rb +138 -0
  48. data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
  49. data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
  50. data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
  51. data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
  52. data/spec/lutaml/model/serializable_spec.rb +297 -0
  53. data/spec/lutaml/model/serializable_validation_spec.rb +85 -0
  54. data/spec/lutaml/model/simple_model_spec.rb +314 -0
  55. data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
  56. data/spec/lutaml/model/type/boolean_spec.rb +54 -0
  57. data/spec/lutaml/model/type/date_spec.rb +118 -0
  58. data/spec/lutaml/model/type/date_time_spec.rb +127 -0
  59. data/spec/lutaml/model/type/decimal_spec.rb +125 -0
  60. data/spec/lutaml/model/type/float_spec.rb +191 -0
  61. data/spec/lutaml/model/type/hash_spec.rb +63 -0
  62. data/spec/lutaml/model/type/integer_spec.rb +145 -0
  63. data/spec/lutaml/model/type/string_spec.rb +150 -0
  64. data/spec/lutaml/model/type/time_spec.rb +142 -0
  65. data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
  66. data/spec/lutaml/model/type_spec.rb +276 -0
  67. data/spec/lutaml/model/utils_spec.rb +79 -0
  68. data/spec/lutaml/model/validation_spec.rb +83 -0
  69. data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
  70. data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
  71. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
  72. data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
  73. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
  74. data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
  75. data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
  76. data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
  77. data/spec/lutaml/model_spec.rb +1 -0
  78. data/spec/person_spec.rb +161 -0
  79. data/spec/spec_helper.rb +33 -0
  80. 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