firm 0.9.8 → 1.0.0
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/README.md +9 -8
- data/lib/firm/serialize/core.rb +10 -2
- data/lib/firm/serializer/json.rb +28 -14
- data/lib/firm/serializer/xml.rb +23 -19
- data/lib/firm/serializer/yaml.rb +7 -2
- data/lib/firm/version.rb +1 -1
- data/tests/serializer_tests.rb +58 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ebb1f6594b3ecc4e56863a0f04ce3eb44fc5bfad0218a183298b44e56e9cf8e
|
4
|
+
data.tar.gz: 4e2349b45ae0479c968406fcd2eafb7ac4e76c8317cbbeeea20ecb784e998115
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 247ca7e7c9eb24fc5ca31c8cfb3f06ec9e4c3779627f3e35722d2c51a630c255cc4d182d434d35da7207f112b0905635a5717fd767f3c904a251b7ab84873cbd
|
7
|
+
data.tar.gz: 2953c510c88ed9078f777a0c8e23399f48339ef5f6c85fcf264fe025de795ecc187407d7d7bddf77b89dbb182eb158123e62aad909c91fbfffc04f613cf8280f
|
data/README.md
CHANGED
@@ -8,11 +8,11 @@
|
|
8
8
|
|
9
9
|
## Introduction
|
10
10
|
|
11
|
-
FIRM is a pure Ruby library that works across different Ruby implementations like MRI Ruby and JRuby providing
|
12
|
-
|
11
|
+
FIRM is a pure Ruby library that works across different Ruby implementations like MRI Ruby and JRuby providing format
|
12
|
+
independent object (de-)serialization support.
|
13
13
|
|
14
14
|
FIRM is explicitly **NOT** intended as a non-discriminative marshaling library (dumping any object's attributes)
|
15
|
-
but rather as structured and safe serialization library requiring users to think about what state they want
|
15
|
+
but rather as a structured and safe serialization library requiring users to think about what state they want
|
16
16
|
persisted (and possibly in what form) and what not.
|
17
17
|
Straightforward attribute serialization is simple with minimal intrusion on user code.
|
18
18
|
In addition various customization options are available to tweak (de-)serialization for a perfect fit if needed.
|
@@ -39,20 +39,21 @@ FIRM supports (de-)serializing many core Ruby objects out of the box including:
|
|
39
39
|
- `Time`
|
40
40
|
- `Struct`
|
41
41
|
- `Set`
|
42
|
-
- `OpenStruct`
|
42
|
+
- `OpenStruct` (optional starting from Ruby 3.5 which removes this class from the standard library)
|
43
43
|
- `Date`
|
44
44
|
- `DateTime`
|
45
45
|
|
46
|
-
For security reasons FIRM does **not** support direct (de-)serializing of `Class` objects but will rather
|
46
|
+
For simplicity and security reasons FIRM does **not** support direct (de-)serializing of `Class` objects but will rather
|
47
47
|
serialize (and deserialize) these as their scoped string names. Customized property setters can be used to
|
48
48
|
resolve Class objects from these names if really needed.
|
49
49
|
|
50
|
-
|
50
|
+
Serialization support for user defined classes is available through a simple DSL scheme.
|
51
51
|
|
52
52
|
FIRM provides object aliasing support for JSON and XML in a similar fashion as the standard support provided
|
53
|
-
by YAML
|
53
|
+
by YAML.<br>
|
54
|
+
In addition FIRM automatically recognizes and handles cyclic references of aliasable objects.
|
54
55
|
|
55
|
-
FIRM also
|
56
|
+
FIRM serialization is also thread safe and supports re-entrancy (i.e. nested serialization).
|
56
57
|
|
57
58
|
## Installing FIRM
|
58
59
|
|
data/lib/firm/serialize/core.rb
CHANGED
@@ -32,12 +32,20 @@ module FIRM
|
|
32
32
|
end
|
33
33
|
|
34
34
|
require 'set'
|
35
|
-
|
35
|
+
# from Ruby 3.5.0 OpenStruct will not be available by default anymore
|
36
|
+
begin
|
37
|
+
require 'ostruct'
|
38
|
+
rescue LoadError
|
39
|
+
end
|
36
40
|
|
37
|
-
[::Array, ::Hash, ::Struct, ::Range, ::Rational, ::Complex, ::Regexp, ::Set, ::
|
41
|
+
[::Array, ::Hash, ::Struct, ::Range, ::Rational, ::Complex, ::Regexp, ::Set, ::Time, ::Date, ::DateTime].each do |c|
|
38
42
|
c.include FIRM::Serializable::CoreExt
|
39
43
|
end
|
40
44
|
|
45
|
+
if ::Object.const_defined?(:OpenStruct)
|
46
|
+
::OpenStruct.include FIRM::Serializable::CoreExt
|
47
|
+
end
|
48
|
+
|
41
49
|
if ::Object.const_defined?(:BigDecimal)
|
42
50
|
::BigDecimal.include FIRM::Serializable::CoreExt
|
43
51
|
end
|
data/lib/firm/serializer/json.rb
CHANGED
@@ -14,7 +14,12 @@ require 'json/add/bigdecimal' if ::Object.const_defined?(:BigDecimal)
|
|
14
14
|
require 'json/add/rational'
|
15
15
|
require 'json/add/complex'
|
16
16
|
require 'json/add/set'
|
17
|
-
|
17
|
+
# from Ruby 3.5.0 OpenStruct will not be available by default anymore
|
18
|
+
begin
|
19
|
+
require 'ostruct'
|
20
|
+
require 'json/add/ostruct'
|
21
|
+
rescue LoadError
|
22
|
+
end
|
18
23
|
|
19
24
|
module FIRM
|
20
25
|
|
@@ -22,6 +27,8 @@ module FIRM
|
|
22
27
|
|
23
28
|
module JSON
|
24
29
|
|
30
|
+
CREATE_ID = 'rbklass'.freeze
|
31
|
+
|
25
32
|
# Derived Hash class to use for deserialized JSON object data which
|
26
33
|
# supports using Symbol keys.
|
27
34
|
class ObjectHash < ::Hash
|
@@ -107,7 +114,8 @@ module FIRM
|
|
107
114
|
class << self
|
108
115
|
def serializables
|
109
116
|
set = ::Set.new( [::NilClass, ::TrueClass, ::FalseClass, ::Integer, ::Float, ::String, ::Array, ::Hash,
|
110
|
-
::Date, ::DateTime, ::Range, ::Rational, ::Complex, ::Regexp, ::Struct, ::Symbol, ::Time, ::Set
|
117
|
+
::Date, ::DateTime, ::Range, ::Rational, ::Complex, ::Regexp, ::Struct, ::Symbol, ::Time, ::Set])
|
118
|
+
set << ::OpenStruct if ::Object.const_defined?(:OpenStruct)
|
111
119
|
set << ::BigDecimal if ::Object.const_defined?(:BigDecimal)
|
112
120
|
set
|
113
121
|
end
|
@@ -156,6 +164,8 @@ module FIRM
|
|
156
164
|
begin
|
157
165
|
# initialize anchor registry
|
158
166
|
Serializable::Aliasing.start_anchor_object_registry
|
167
|
+
# set custom (more compact) create_id
|
168
|
+
::JSON.create_id = Serializable::JSON::CREATE_ID
|
159
169
|
for_json = obj.respond_to?(:as_json) ? obj.as_json : obj
|
160
170
|
if pretty
|
161
171
|
if io || io.respond_to?(:write)
|
@@ -179,6 +189,8 @@ module FIRM
|
|
179
189
|
Serializable::Aliasing.start_anchor_references
|
180
190
|
# enable safe deserializing
|
181
191
|
self.start_safe_deserialize
|
192
|
+
# set custom (more compact) create_id
|
193
|
+
::JSON.create_id = Serializable::JSON::CREATE_ID
|
182
194
|
::JSON.parse!(source,
|
183
195
|
create_additions: true,
|
184
196
|
object_class: Serializable::JSON::ObjectHash)
|
@@ -407,21 +419,23 @@ class ::Struct
|
|
407
419
|
end
|
408
420
|
end
|
409
421
|
|
410
|
-
|
411
|
-
|
422
|
+
if ::Object.const_defined?(:OpenStruct)
|
423
|
+
class ::OpenStruct
|
424
|
+
include FIRM::Serializable::JSON::ContainerPatch
|
412
425
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
426
|
+
class << self
|
427
|
+
# Create a new OpenStruct instance from deserialized JSON data.
|
428
|
+
# @param [Hash] object deserialized JSON object
|
429
|
+
# @return [OpenStruct] restored OpenStruct instance
|
430
|
+
def json_create(object)
|
431
|
+
json_new(object) { |instance| object['t'].each { |k,v| instance[k] = v } }
|
432
|
+
end
|
419
433
|
end
|
420
|
-
end
|
421
434
|
|
422
|
-
|
423
|
-
|
424
|
-
|
435
|
+
def as_json(*)
|
436
|
+
build_json do |json_data|
|
437
|
+
json_data['t'] = table.collect { |k,v| [k.respond_to?(:as_json) ? k.as_json : k, v.respond_to?(:as_json) ? v.as_json : v] }
|
438
|
+
end
|
425
439
|
end
|
426
440
|
end
|
427
441
|
end
|
data/lib/firm/serializer/xml.rb
CHANGED
@@ -3,7 +3,11 @@
|
|
3
3
|
|
4
4
|
|
5
5
|
require 'set'
|
6
|
-
|
6
|
+
# from Ruby 3.5.0 OpenStruct will not be available by default anymore
|
7
|
+
begin
|
8
|
+
require 'ostruct'
|
9
|
+
rescue LoadError
|
10
|
+
end
|
7
11
|
require 'date'
|
8
12
|
|
9
13
|
module FIRM
|
@@ -60,7 +64,7 @@ module FIRM
|
|
60
64
|
def to_xml(_, _)
|
61
65
|
raise Serializable::Exception, "Missing serialization method for #{klass} XML handler"
|
62
66
|
end
|
63
|
-
def from_xml(
|
67
|
+
def from_xml(_xml)
|
64
68
|
raise Serializable::Exception, "Missing serialization method for #{klass} XML handler"
|
65
69
|
end
|
66
70
|
end
|
@@ -181,7 +185,7 @@ module FIRM
|
|
181
185
|
create_type_node(xml).add_child(Nokogiri::XML::CDATA.new(xml.document, value.name))
|
182
186
|
xml
|
183
187
|
end
|
184
|
-
def from_xml(
|
188
|
+
def from_xml(_xml)
|
185
189
|
# should never be called
|
186
190
|
raise Serializable::Exception, 'Unsupported Class deserialization'
|
187
191
|
end
|
@@ -291,10 +295,9 @@ module FIRM
|
|
291
295
|
end
|
292
296
|
def from_xml(xml)
|
293
297
|
create_from_xml(xml) do |instance|
|
294
|
-
xml.elements.
|
298
|
+
xml.elements.each do |pair|
|
295
299
|
k, v = pair.elements
|
296
300
|
instance[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
|
297
|
-
instance
|
298
301
|
end
|
299
302
|
end
|
300
303
|
end
|
@@ -434,22 +437,23 @@ module FIRM
|
|
434
437
|
end
|
435
438
|
end
|
436
439
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
value
|
441
|
-
|
442
|
-
|
443
|
-
|
440
|
+
if ::Object.const_defined?(:OpenStruct)
|
441
|
+
define_xml_handler(::OpenStruct, aliasable: true) do
|
442
|
+
def to_xml(xml, value)
|
443
|
+
build_xml(xml, value) do |node|
|
444
|
+
value.each_pair do |k,v|
|
445
|
+
pair = node.add_child(Nokogiri::XML::Node.new('P', node.document))
|
446
|
+
Serializable::XML.to_xml(pair, k)
|
447
|
+
Serializable::XML.to_xml(pair, v)
|
448
|
+
end
|
444
449
|
end
|
445
450
|
end
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
instance
|
451
|
+
def from_xml(xml)
|
452
|
+
create_from_xml(xml) do |instance|
|
453
|
+
xml.elements.each do |pair|
|
454
|
+
k, v = pair.elements
|
455
|
+
instance[Serializable::XML.from_xml(k)] = Serializable::XML.from_xml(v)
|
456
|
+
end
|
453
457
|
end
|
454
458
|
end
|
455
459
|
end
|
data/lib/firm/serializer/yaml.rb
CHANGED
@@ -5,7 +5,11 @@
|
|
5
5
|
require 'yaml'
|
6
6
|
require 'date'
|
7
7
|
require 'set'
|
8
|
-
|
8
|
+
# from Ruby 3.5.0 OpenStruct will not be available by default anymore
|
9
|
+
begin
|
10
|
+
require 'ostruct'
|
11
|
+
rescue LoadError
|
12
|
+
end
|
9
13
|
|
10
14
|
module FIRM
|
11
15
|
|
@@ -15,7 +19,8 @@ module FIRM
|
|
15
19
|
|
16
20
|
class << self
|
17
21
|
def serializables
|
18
|
-
list = [::Date, ::DateTime, ::Range, ::Rational, ::Complex, ::Regexp, ::Struct, ::Symbol, ::Time, ::Set
|
22
|
+
list = [::Date, ::DateTime, ::Range, ::Rational, ::Complex, ::Regexp, ::Struct, ::Symbol, ::Time, ::Set]
|
23
|
+
list.push(::OpenStruct) if ::Object.const_defined?(:OpenStruct)
|
19
24
|
list.push(::BigDecimal) if ::Object.const_defined?(:BigDecimal)
|
20
25
|
list
|
21
26
|
end
|
data/lib/firm/version.rb
CHANGED
data/tests/serializer_tests.rb
CHANGED
@@ -1084,4 +1084,62 @@ module SerializerTestMixin
|
|
1084
1084
|
assert_equal(obj.symbol, obj_new.symbol)
|
1085
1085
|
end
|
1086
1086
|
|
1087
|
+
def run_test_threads
|
1088
|
+
data = { list: ::Array.new(5, [ PropTest.new, Point.new(0, 0), Point.new(10, 10), Point.new(100, 400), Rect.new(20, 20, 40, 40) ]) }
|
1089
|
+
results = []
|
1090
|
+
threads = ::Array.new(10) do
|
1091
|
+
Thread.new do
|
1092
|
+
results << run_test_fibers(data)
|
1093
|
+
end
|
1094
|
+
end
|
1095
|
+
threads.each { |t| t.join }
|
1096
|
+
results
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
def run_test_fibers(data)
|
1100
|
+
fibers = ::Array.new(100) do |_|
|
1101
|
+
Fiber.new do
|
1102
|
+
s = assert_nothing_raised { data.serialize }
|
1103
|
+
Fiber.yield nil
|
1104
|
+
new_data = assert_nothing_raised { FIRM.deserialize(s) }
|
1105
|
+
Fiber.yield nil
|
1106
|
+
assert_instance_of(::Hash, new_data)
|
1107
|
+
assert_instance_of(::Array, new_data[:list])
|
1108
|
+
assert_equal(5, new_data[:list].size)
|
1109
|
+
assert_true(new_data[:list].all? { |e| e.is_a?(::Array) && e.size == 5 })
|
1110
|
+
5.times { |i| assert_equal(data[:list].first[i], new_data[:list].first[i]) }
|
1111
|
+
4.times do |n|
|
1112
|
+
5.times { |i| assert_equal(new_data[:list].first[i].object_id, new_data[:list][n+1][i].object_id) }
|
1113
|
+
end
|
1114
|
+
new_data
|
1115
|
+
end
|
1116
|
+
end
|
1117
|
+
results = []
|
1118
|
+
begin
|
1119
|
+
fibers = fibers.select do |fiber|
|
1120
|
+
if (rc = fiber.resume)
|
1121
|
+
results << rc
|
1122
|
+
false
|
1123
|
+
else
|
1124
|
+
true
|
1125
|
+
end
|
1126
|
+
end
|
1127
|
+
end until fibers.empty?
|
1128
|
+
results
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
def test_threading
|
1132
|
+
|
1133
|
+
results = run_test_threads
|
1134
|
+
|
1135
|
+
set = results.inject(::Set.new) do |set, fiber_results|
|
1136
|
+
fiber_results.inject(set) { |set_, fiber_result| set_.merge(fiber_result[:list][1].collect { |o| o.object_id }) }
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
# although we started with a single unique dataset, distributing that through 10 threads * 100 fibers
|
1140
|
+
# to serialize and deserialize should result in 1000 distinct datasets with each 5 distinct data instances
|
1141
|
+
assert_equal(5000, set.size)
|
1142
|
+
|
1143
|
+
end
|
1144
|
+
|
1087
1145
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: firm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Corino
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|