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 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