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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 32e4d13b19619a0c3f381595d5942f9e915ee837
4
- data.tar.gz: 3968b3f4dd481c15a878465214e1db635d42b616
3
+ metadata.gz: 3195187f64cf0bc3c2d9499028b45d66a19591f2
4
+ data.tar.gz: 1d20e7ef25821a865001a5e59237418749e87aa1
5
5
  SHA512:
6
- metadata.gz: e0c09429505321937097e3dc71bbb1c4d3029ddffd9b9dfdbfaf1bdbdf9133b929b44141558ba435d720ae4f318e15615b5f41de41ebbea9963370fe2a9f9111
7
- data.tar.gz: 9be9e4d4bb3d9f246f32440e1eb97f4a5d12c0de700007f21c6f0e41ab9546f54237e7aa756ee96e435de54f8892f10599a34d3059c4c74b0e9e21c762292781
6
+ metadata.gz: 1c1a5fcb71deda8629cdc4806499cdc050b4900af1ae65e67179e8245ce263db67f89bbf8e3fcc29bdb39c8168b688490a77591a7429d7851ae921e6f3398ce3
7
+ data.tar.gz: 7bf8bc70835a72adbc41f012c21bed66fee6afdaae78a81fd95a65591b6f6615ba720612fec287e6980b053aa793f33c8c2d6a564b2c23e506aa84230210487e
data/lib/rlp/data.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  ##
2
2
  # A wrapper to represent already RLP encoded data
3
-
3
+ #
4
4
  module RLP
5
5
  class Data < String
6
6
  end
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
@@ -1,3 +1,3 @@
1
1
  module RLP
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
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.2.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