firm 0.9.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|