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