rlp 0.2.0 → 0.3.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/lib/rlp/data.rb +1 -1
- data/lib/rlp/encode.rb +34 -0
- data/lib/rlp/error.rb +81 -0
- data/lib/rlp/sedes.rb +13 -1
- data/lib/rlp/sedes/serializable.rb +127 -0
- data/lib/rlp/version.rb +1 -1
- metadata +16 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3195187f64cf0bc3c2d9499028b45d66a19591f2
|
4
|
+
data.tar.gz: 1d20e7ef25821a865001a5e59237418749e87aa1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c1a5fcb71deda8629cdc4806499cdc050b4900af1ae65e67179e8245ce263db67f89bbf8e3fcc29bdb39c8168b688490a77591a7429d7851ae921e6f3398ce3
|
7
|
+
data.tar.gz: 7bf8bc70835a72adbc41f012c21bed66fee6afdaae78a81fd95a65591b6f6615ba720612fec287e6980b053aa793f33c8c2d6a564b2c23e506aa84230210487e
|
data/lib/rlp/data.rb
CHANGED
data/lib/rlp/encode.rb
CHANGED
@@ -4,6 +4,40 @@ module RLP
|
|
4
4
|
include Error
|
5
5
|
include Utils
|
6
6
|
|
7
|
+
##
|
8
|
+
# Encode a Ruby object in RLP format.
|
9
|
+
#
|
10
|
+
# By default, the object is serialized in a suitable way first (using
|
11
|
+
# {RLP::Sedes.infer}) and then encoded. Serialization can be explicitly
|
12
|
+
# suppressed by setting {RLP::Sedes.infer} to `false` and not passing an
|
13
|
+
# alternative as `sedes`.
|
14
|
+
#
|
15
|
+
# If `obj` has an attribute `_cached_rlp` (as, notably,
|
16
|
+
# {RLP::Serializable}) and its value is not `nil`, this value is returned
|
17
|
+
# bypassing serialization and encoding, unless `sedes` is given (as the
|
18
|
+
# cache is assumed to refer to the standard serialization which can be
|
19
|
+
# replaced by specifying `sedes`).
|
20
|
+
#
|
21
|
+
# If `obj` is a {RLP::Serializable} and `cache` is true, the result of the
|
22
|
+
# encoding will be stored in `_cached_rlp` if it is empty and
|
23
|
+
# {RLP::Serializable.make_immutable} will be invoked on `obj`.
|
24
|
+
#
|
25
|
+
# @param obj [Object] object to encode
|
26
|
+
# @param sedes [#serialize(obj)] an object implementing a function
|
27
|
+
# `serialize(obj)` which will be used to serialize `obj` before
|
28
|
+
# encoding, or `nil` to use the infered one (if any)
|
29
|
+
# @param infer_serializer [Boolean] if `true` an appropriate serializer
|
30
|
+
# will be selected using {RLP::Sedes.infer} to serialize `obj` before
|
31
|
+
# encoding
|
32
|
+
# @param cache [Boolean] cache the return value in `obj._cached_rlp` if
|
33
|
+
# possible and make `obj` immutable (default `false`)
|
34
|
+
#
|
35
|
+
# @return [String] the RLP encoded item
|
36
|
+
#
|
37
|
+
# @raise [RLP::EncodingError] in the rather unlikely case that the item
|
38
|
+
# is too big to encode (will not happen)
|
39
|
+
# @raise [RLP::SerializationError] if the serialization fails
|
40
|
+
#
|
7
41
|
def encode(obj, sedes: nil, infer_serializer: true, cache: false)
|
8
42
|
# TODO: cache flow
|
9
43
|
|
data/lib/rlp/error.rb
CHANGED
@@ -4,6 +4,8 @@ module RLP
|
|
4
4
|
class RLPException < Exception; end
|
5
5
|
|
6
6
|
class EncodingError < RLPException
|
7
|
+
attr :obj
|
8
|
+
|
7
9
|
def initialize(message, obj)
|
8
10
|
super(message)
|
9
11
|
|
@@ -12,6 +14,8 @@ module RLP
|
|
12
14
|
end
|
13
15
|
|
14
16
|
class DecodingError < RLPException
|
17
|
+
attr :rlp
|
18
|
+
|
15
19
|
def initialize(message, rlp)
|
16
20
|
super(message)
|
17
21
|
|
@@ -20,6 +24,8 @@ module RLP
|
|
20
24
|
end
|
21
25
|
|
22
26
|
class SerializationError < RLPException
|
27
|
+
attr :obj
|
28
|
+
|
23
29
|
def initialize(message, obj)
|
24
30
|
super(message)
|
25
31
|
|
@@ -28,6 +34,8 @@ module RLP
|
|
28
34
|
end
|
29
35
|
|
30
36
|
class DeserializationError < RLPException
|
37
|
+
attr :serial
|
38
|
+
|
31
39
|
def initialize(message, serial)
|
32
40
|
super(message)
|
33
41
|
|
@@ -36,6 +44,8 @@ module RLP
|
|
36
44
|
end
|
37
45
|
|
38
46
|
class ListSerializationError < SerializationError
|
47
|
+
attr :index, :element_exception
|
48
|
+
|
39
49
|
def initialize(message: nil, obj: nil, element_exception: nil, index: nil)
|
40
50
|
if message.nil?
|
41
51
|
raise ArgumentError, "index and element_exception must be present" if index.nil? || element_exception.nil?
|
@@ -50,6 +60,8 @@ module RLP
|
|
50
60
|
end
|
51
61
|
|
52
62
|
class ListDeserializationError < DeserializationError
|
63
|
+
attr :index, :element_exception
|
64
|
+
|
53
65
|
def initialize(message: nil, serial: nil, element_exception: nil, index: nil)
|
54
66
|
if message.nil?
|
55
67
|
raise ArgumentError, "index and element_exception must be present" if index.nil? || element_exception.nil?
|
@@ -63,5 +75,74 @@ module RLP
|
|
63
75
|
end
|
64
76
|
end
|
65
77
|
|
78
|
+
##
|
79
|
+
# Exception raised if serialization of a {RLP::Sedes::Serializable} object
|
80
|
+
# fails.
|
81
|
+
#
|
82
|
+
class ObjectSerializationError < SerializationError
|
83
|
+
attr :field, :list_exception
|
84
|
+
|
85
|
+
##
|
86
|
+
# @param sedes [RLP::Sedes::Serializable] the sedes that failed
|
87
|
+
# @param list_exception [RLP::Error::ListSerializationError] exception raised by the underlying
|
88
|
+
# list sedes, or `nil` if no exception has been raised
|
89
|
+
#
|
90
|
+
def initialize(message: nil, obj: nil, sedes: nil, list_exception: nil)
|
91
|
+
if message.nil?
|
92
|
+
raise ArgumentError, "list_exception and sedes must be present" if list_exception.nil? || sedes.nil?
|
93
|
+
|
94
|
+
if list_exception.element_exception
|
95
|
+
field = sedes.serializable_fields.keys[list_exception.index]
|
96
|
+
message = "Serialization failed because of field #{field} ('#{list_exception.element_exception}')"
|
97
|
+
else
|
98
|
+
field = nil
|
99
|
+
message = "Serialization failed because of underlying list ('#{list_exception}')"
|
100
|
+
end
|
101
|
+
else
|
102
|
+
field = nil
|
103
|
+
end
|
104
|
+
|
105
|
+
super(message, obj)
|
106
|
+
|
107
|
+
@field = field
|
108
|
+
@list_exception = list_exception
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Exception raised if deserialization by a {RLP::Sedes::Serializable} fails.
|
114
|
+
#
|
115
|
+
class ObjectDeserializationError < DeserializationError
|
116
|
+
attr :sedes, :field, :list_exception
|
117
|
+
|
118
|
+
##
|
119
|
+
# @param sedes [RLP::Sedes::Serializable] the sedes that failed
|
120
|
+
# @param list_exception [RLP::ListDeserializationError] exception raised
|
121
|
+
# by the underlying list sedes, or `nil` if no such exception has been
|
122
|
+
# raised
|
123
|
+
#
|
124
|
+
def initialize(message: nil, serial: nil, sedes: nil, list_exception: nil)
|
125
|
+
if message.nil?
|
126
|
+
raise ArgumentError, "list_exception must be present" if list_exception.nil?
|
127
|
+
|
128
|
+
if list_exception.element_exception
|
129
|
+
raise ArgumentError, "sedes must be present" if sedes.nil?
|
130
|
+
|
131
|
+
field = sedes.serializable_fields.keys[list_exception.index]
|
132
|
+
message = "Deserialization failed because of field #{field} ('#{list_exception.element_exception}')"
|
133
|
+
else
|
134
|
+
field = nil
|
135
|
+
message = "Deserialization failed because of underlying list ('#{list_exception}')"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
super(message, serial)
|
140
|
+
|
141
|
+
@sedes = sedes
|
142
|
+
@field = field
|
143
|
+
@list_exception = list_exception
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
66
147
|
end
|
67
148
|
end
|
data/lib/rlp/sedes.rb
CHANGED
@@ -3,13 +3,25 @@ require_relative 'sedes/binary'
|
|
3
3
|
require_relative 'sedes/list'
|
4
4
|
require_relative 'sedes/countable_list'
|
5
5
|
require_relative 'sedes/raw'
|
6
|
+
require_relative 'sedes/serializable'
|
6
7
|
|
7
8
|
module RLP
|
8
9
|
module Sedes
|
9
10
|
|
10
11
|
class <<self
|
12
|
+
##
|
13
|
+
# Try to find a sedes objects suitable for a given Ruby object.
|
14
|
+
#
|
15
|
+
# The sedes objects considered are `obj`'s class, `big_endian_int` and
|
16
|
+
# `binary`. If `obj` is a list, a `RLP::Sedes::List` will be constructed
|
17
|
+
# recursively.
|
18
|
+
#
|
19
|
+
# @param obj [Object] the Ruby object for which to find a sedes object
|
20
|
+
#
|
21
|
+
# @raise [TypeError] if no appropriate sedes could be found
|
22
|
+
#
|
11
23
|
def infer(obj)
|
12
|
-
return obj if sedes?(obj)
|
24
|
+
return obj.class if sedes?(obj.class)
|
13
25
|
return big_endian_int if obj.is_a?(Integer) && obj >= 0
|
14
26
|
return binary if Binary.valid_type?(obj)
|
15
27
|
return List.new(elements: obj.map {|item| infer(item) }) if RLP.list?(obj)
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module RLP
|
2
|
+
module Sedes
|
3
|
+
##
|
4
|
+
# Mixin for objects which can be serialized into RLP lists.
|
5
|
+
#
|
6
|
+
# `fields` defines which attributes are serialized and how this is done. It
|
7
|
+
# is expected to be a hash in the form of `name => sedes`. Here, `name` is
|
8
|
+
# the name of an attribute and `sedes` is the sedes object that will be used
|
9
|
+
# to serialize the corresponding attribute. The object as a whole is then
|
10
|
+
# serialized as a list of those fields.
|
11
|
+
#
|
12
|
+
module Serializable
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
include Error
|
16
|
+
|
17
|
+
def set_serializable_fields(fields)
|
18
|
+
raise "Cannot override serializable fields!" if @serializable_fields
|
19
|
+
|
20
|
+
@serializable_fields = fields
|
21
|
+
|
22
|
+
fields.keys.each do |field|
|
23
|
+
class_eval <<-ATTR
|
24
|
+
def #{field}
|
25
|
+
@#{field}
|
26
|
+
end
|
27
|
+
|
28
|
+
def #{field}=(v)
|
29
|
+
_set_field(:#{field}, v)
|
30
|
+
end
|
31
|
+
ATTR
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def serializable_fields
|
36
|
+
@serializable_fields
|
37
|
+
end
|
38
|
+
|
39
|
+
def serializable_sedes
|
40
|
+
@serializable_sedes ||= Sedes::List.new(elements: serializable_fields.values)
|
41
|
+
end
|
42
|
+
|
43
|
+
def serialize(obj)
|
44
|
+
begin
|
45
|
+
field_values = serializable_fields.keys.map {|k| obj.send k }
|
46
|
+
rescue NoMethodError => e
|
47
|
+
raise ObjectSerializationError.new(message: "Cannot serialize this object (missing attribute)", obj: obj)
|
48
|
+
end
|
49
|
+
|
50
|
+
begin
|
51
|
+
serializable_sedes.serialize(field_values)
|
52
|
+
rescue ListSerializationError => e
|
53
|
+
raise ObjectSerializationError.new(obj: obj, sedes: self, list_exception: e)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def deserialize(serial, exclude: nil, extra: {})
|
58
|
+
begin
|
59
|
+
values = serializable_sedes.deserialize(serial)
|
60
|
+
rescue ListDeserializationError => e
|
61
|
+
raise ObjectDeserializationError.new(serial: serial, sedes: self, list_exception: e)
|
62
|
+
end
|
63
|
+
|
64
|
+
params = Hash[*serializable_fields.keys.zip(values).flatten(1)]
|
65
|
+
params.delete_if {|field, value| exclude.include?(field) } if exclude
|
66
|
+
|
67
|
+
obj = self.new params.merge(extra)
|
68
|
+
obj.instance_variable_set :@_mutable, false
|
69
|
+
obj
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class <<self
|
74
|
+
def included(base)
|
75
|
+
base.extend ClassMethods
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(*args)
|
80
|
+
serializable_initialize(*args)
|
81
|
+
end
|
82
|
+
|
83
|
+
def serializable_initialize(*args)
|
84
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
85
|
+
|
86
|
+
field_set = self.class.serializable_fields.keys
|
87
|
+
|
88
|
+
self.class.serializable_fields.keys.zip(args).each do |(field, arg)|
|
89
|
+
break unless arg
|
90
|
+
_set_field field, arg
|
91
|
+
field_set.delete field
|
92
|
+
end
|
93
|
+
|
94
|
+
options.each do |field, value|
|
95
|
+
if field_set.include?(field)
|
96
|
+
_set_field field, value
|
97
|
+
field_set.delete field
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
raise TypeError, "Not all fields initialized" unless field_set.size == 0
|
102
|
+
end
|
103
|
+
|
104
|
+
def ==(other)
|
105
|
+
return false unless other.class.respond_to?(:serialize)
|
106
|
+
self.class.serialize(self) == other.class.serialize(other)
|
107
|
+
end
|
108
|
+
|
109
|
+
def _set_field(field, value)
|
110
|
+
unless instance_variable_defined?(:@_mutable)
|
111
|
+
@_mutable = true
|
112
|
+
end
|
113
|
+
|
114
|
+
if _mutable? || !self.class.serializable_fields.has_key?(field)
|
115
|
+
instance_variable_set :"@#{field}", value
|
116
|
+
else
|
117
|
+
raise ArgumentError, "Tried to mutate immutable object"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def _mutable?
|
122
|
+
@_mutable
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/rlp/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rlp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Xie
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 5.8.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: yard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.8.7.6
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.8.7.6
|
41
55
|
description: A Ruby implementation of Ethereum's Recursive Length Prefix encoding
|
42
56
|
(RLP).
|
43
57
|
email:
|
@@ -60,6 +74,7 @@ files:
|
|
60
74
|
- lib/rlp/sedes/countable_list.rb
|
61
75
|
- lib/rlp/sedes/list.rb
|
62
76
|
- lib/rlp/sedes/raw.rb
|
77
|
+
- lib/rlp/sedes/serializable.rb
|
63
78
|
- lib/rlp/utils.rb
|
64
79
|
- lib/rlp/version.rb
|
65
80
|
homepage: https://github.com/janx/ruby-rlp
|