yasl 0.2.0 → 0.2.1

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
  SHA256:
3
- metadata.gz: 5c4491f82bad72b2981e4bbbf35001f30c95be844ebf4bc3c8f904e019d1e0cd
4
- data.tar.gz: eb27247b7913c181fb409f8e07f8735d42bf1ac73e55fb6905ed9c931826449b
3
+ metadata.gz: ad1fc5f516869a322107b0bb86c96cf46ea789960e920a14fefbf4173294a52f
4
+ data.tar.gz: 1bacb014f802c4295ebaa4b4db5ca6a99120f1cd10ac4f469cf77da2457cfcaa
5
5
  SHA512:
6
- metadata.gz: 199b1226c4a33b6bc4c1daa6ab04c34c7beecfc2c8eb82bdf27d7cc434b7c2537f1d0b806886f2f1dcd83df1a3a55d5af9be9a9b589d5cc7c06b39d508c01446
7
- data.tar.gz: eb167b327b5041ff1087381d2f999c9d42f8942ff617ef850700a3be282732a017af0e9cdcfe04e0527979c531bfc9db31d250e64a82a8c46a91af5e9f5c36f6
6
+ metadata.gz: 97579e85d7bdd8e7d7feec52e0ce6567782c67466dffe103e95261d52628d1c0e3b4a1f40897d39401e5d05d2123ea22b7f4ff98f5134f76a46e32b5103a64a4
7
+ data.tar.gz: 5f7d04c0a0e821d88bf3cafdba3c497df92f1dd92d4f2e8c8222f5c903cbc866767666d10ee8b09d0fd84d05ad03105ba9fc7f0784e4521060a7aa671005bf77
@@ -1,5 +1,14 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.2.1
4
+
5
+ - Make `YASL::UNSERIALIZABLE_DATA_TYPES` work with regular classes too
6
+ - Make load `whitelist_classes` work with string class names
7
+ - Make load `whitelist_classes` work with a single class or class name string
8
+ - Support deserialization fallback of instance variables for Struct member values
9
+ - Added missing hash method to optional pure Ruby Struct implementation
10
+ - Fixed issue with requiring one arg minimum, using `select`, and using undefined `upcase?` in implementation of optional pure Ruby Struct
11
+
3
12
  ## 0.2.0
4
13
 
5
14
  - Support Boolean serialization in Opal (instead of TrueClass and FalseClass)
data/README.md CHANGED
@@ -11,7 +11,7 @@ A pure Ruby serialization library that works across different Ruby implementatio
11
11
  ## Requirements
12
12
 
13
13
  - Portablity across different Ruby implementations, especially [Opal](https://opalrb.com/) in the web browser and [JRuby](https://www.jruby.org/).
14
- - Zero required configuration. Developers are too busy solving business domain problems to worry about low-level serialization details.
14
+ - Zero required configuration. Developers are too busy solving business domain problems to worry about low-level serialization details (no mucking around `as_json` methods and serializer classes).
15
15
  - Silently ignore non-serializable objects (unlike Marshal), such as `Proc`, `Binding`, and `IO`.
16
16
  - No special performance requirements. No high throughput usage. Average Internet speeds.
17
17
  - Ensure system safety through secure deserialization.
@@ -26,7 +26,7 @@ Run:
26
26
  Or add to Gemfile:
27
27
 
28
28
  ```ruby
29
- gem 'yasl', '~> 0.2.0'
29
+ gem 'yasl', '~> 0.2.1'
30
30
  ```
31
31
 
32
32
  And, run:
@@ -315,8 +315,6 @@ To avoid some JS and `keyword_init` issues with `Struct` in [Opal](https://opalr
315
315
  require 'yasl/ext/struct'
316
316
  ```
317
317
 
318
- This ensures successful serialization in YASL.
319
-
320
318
  ## Contributing
321
319
 
322
320
  - Check out the latest master to make sure the feature hasn't been
@@ -333,6 +331,10 @@ This ensures successful serialization in YASL.
333
331
  is fine, but please isolate to its own commit so I can cherry-pick
334
332
  around it.
335
333
 
334
+ ## Software Process
335
+
336
+ [Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md)
337
+
336
338
  ## TODO
337
339
 
338
340
  [TODO.md](TODO.md)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1
@@ -36,14 +36,11 @@ module YASL
36
36
  end
37
37
 
38
38
  def dump_structure(object, for_classes: false)
39
+ return if unserializable?(object)
39
40
  structure = {}
40
41
  if top_level_class?(object, for_classes)
41
- if object.name.nil?
42
- return nil
43
- else
44
- structure[:_class] = object.name
45
- add_to_classes(object)
46
- end
42
+ structure[:_class] = object.name
43
+ add_to_classes(object)
47
44
  elsif YASL.json_basic_data_type?(object)
48
45
  structure = object
49
46
  elsif YASL.ruby_basic_data_type?(object)
@@ -73,22 +70,25 @@ module YASL
73
70
  dump_structure(klass, for_classes: true) unless klass.class_variables.empty? && klass.instance_variables.empty?
74
71
  end
75
72
 
76
- def dump_ruby_basic_data_type_data(object)
77
- case object
78
- when class_ancestor_names_include?('Time')
79
- object.to_datetime.marshal_dump
80
- when class_ancestor_names_include?('Date')
81
- object.marshal_dump
82
- when class_ancestor_names_include?('Complex', 'Rational', 'Regexp', 'Symbol', 'BigDecimal')
83
- object.to_s
84
- when class_ancestor_names_include?('Set')
85
- object.to_a.uniq.map {|element| dump_structure(element) unless unserializable?(element)}
86
- when class_ancestor_names_include?('Range')
87
- [object.begin, object.end, object.exclude_end?]
88
- when class_ancestor_names_include?('Array')
89
- object.map {|element| dump_structure(element) unless unserializable?(element)}
90
- when class_ancestor_names_include?('Hash')
91
- object.reject do |key, value|
73
+ def dump_ruby_basic_data_type_data(obj)
74
+ class_ancestors_names_include = lambda do |*class_names|
75
+ lambda { |obj| class_names.any? { |class_name| obj.class.ancestors.map(&:name).include?(class_name) } }
76
+ end
77
+ case obj
78
+ when class_ancestors_names_include['Time']
79
+ obj.to_datetime.marshal_dump
80
+ when class_ancestors_names_include['Date']
81
+ obj.marshal_dump
82
+ when class_ancestors_names_include['Complex', 'Rational', 'Regexp', 'Symbol', 'BigDecimal']
83
+ obj.to_s
84
+ when class_ancestors_names_include['Set']
85
+ obj.to_a.uniq.map {|element| dump_structure(element)}
86
+ when class_ancestors_names_include['Range']
87
+ [obj.begin, obj.end, obj.exclude_end?]
88
+ when class_ancestors_names_include['Array']
89
+ obj.map {|element| dump_structure(element)}
90
+ when class_ancestors_names_include['Hash']
91
+ obj.reject do |key, value|
92
92
  [key, value].detect {|element| unserializable?(element)}
93
93
  end.map do |pair|
94
94
  pair.map {|element| dump_structure(element)}
@@ -122,10 +122,7 @@ module YASL
122
122
  def dump_class_variables(object)
123
123
  structure = {}
124
124
  if object.respond_to?(:class_variables) && !object.class_variables.empty?
125
- structure[:_class_variables] = object.class_variables.reduce({}) do |class_vars, var|
126
- value = object.class_variable_get(var)
127
- unserializable?(value) ? class_vars : class_vars.merge(var.to_s.sub('@@', '') => dump_structure(value))
128
- end
125
+ structure[:_class_variables] = dump_class_variables_hash(object)
129
126
  structure.delete(:_class_variables) if structure[:_class_variables].empty?
130
127
  end
131
128
  structure
@@ -134,10 +131,7 @@ module YASL
134
131
  def dump_instance_variables(object)
135
132
  structure = {}
136
133
  if !object.instance_variables.empty?
137
- structure[:_instance_variables] = object.instance_variables.sort.reduce({}) do |instance_vars, var|
138
- value = object.instance_variable_get(var)
139
- unserializable?(value) ? instance_vars : instance_vars.merge(var.to_s.sub('@', '') => dump_structure(value))
140
- end
134
+ structure[:_instance_variables] = dump_instance_variables_hash(object)
141
135
  structure.delete(:_instance_variables) if structure[:_instance_variables].empty?
142
136
  end
143
137
  structure
@@ -146,17 +140,14 @@ module YASL
146
140
  def dump_struct_member_values(object)
147
141
  structure = {}
148
142
  if object.is_a?(Struct)
149
- structure[:_struct_member_values] = object.members.reduce({}) do |member_values, member|
150
- value = object[member]
151
- value.nil? || unserializable?(value) ? member_values : member_values.merge(member => dump_structure(value))
152
- end
143
+ structure[:_struct_member_values] = dump_struct_member_values_hash(object)
153
144
  structure.delete(:_struct_member_values) if structure[:_struct_member_values].empty?
154
145
  end
155
146
  structure
156
147
  end
157
148
 
158
149
  def unserializable?(value)
159
- result = UNSERIALIZABLE_DATA_TYPES.detect {|class_name| value.class.ancestors.map(&:name).include?(class_name)}
150
+ result = UNSERIALIZABLE_DATA_TYPES.detect {|class_name| value.class.ancestors.map(&:name).include?(class_name.to_s)}
160
151
  result = ((value.is_a?(Class) || value.is_a?(Module)) && value.name.nil?) if result.nil?
161
152
  result
162
153
  end
@@ -181,6 +172,27 @@ module YASL
181
172
  (object_class_array_index + 1) unless object_class_array_index.nil?
182
173
  end
183
174
 
175
+ def dump_class_variables_hash(object)
176
+ object.class_variables.reduce({}) do |class_vars, var|
177
+ value = object.class_variable_get(var)
178
+ unserializable?(value) ? class_vars : class_vars.merge(var.to_s.sub('@@', '') => dump_structure(value))
179
+ end
180
+ end
181
+
182
+ def dump_instance_variables_hash(object)
183
+ object.instance_variables.sort.reduce({}) do |instance_vars, var|
184
+ value = object.instance_variable_get(var)
185
+ unserializable?(value) ? instance_vars : instance_vars.merge(var.to_s.sub('@', '') => dump_structure(value))
186
+ end
187
+ end
188
+
189
+ def dump_struct_member_values_hash(object)
190
+ object.members.reduce({}) do |member_values, member|
191
+ value = object[member]
192
+ value.nil? || unserializable?(value) ? member_values : member_values.merge(member => dump_structure(value))
193
+ end
194
+ end
195
+
184
196
  def add_to_class_array(object)
185
197
  object_class = class_for(object)
186
198
  class_objects[object_class] ||= []
@@ -188,14 +200,6 @@ module YASL
188
200
  class_objects[object_class].index(object) + 1
189
201
  end
190
202
 
191
- def class_ancestor_names_include?(*class_names)
192
- lambda do |object|
193
- class_names.reduce(false) do |result, class_name|
194
- result || object.class.ancestors.map(&:name).include?(class_name)
195
- end
196
- end
197
- end
198
-
199
203
  end
200
204
 
201
205
  end
@@ -24,10 +24,8 @@ Object.send(:remove_const, :Struct) if Object.constants.include?(:Struct)
24
24
  # Optional re-implmentation of Struct in Pure Ruby (to get around JS issues in Opal Struct)
25
25
  class Struct
26
26
  class << self
27
- def new(*class_name_and_or_attributes, keyword_init: false)
28
- class_name = class_name_and_or_attributes.shift if class_name_and_or_attributes.first.to_s.chars.first.to_s.upcase?
29
- attributes = class_name_and_or_attributes.compact.map(&:to_s).map(&:to_sym)
30
- struct_class = Class.new do
27
+ CLASS_DEFINITION_FOR_ATTRIBUTES = lambda do |attributes, keyword_init|
28
+ lambda do |defined_class|
31
29
  members_array = attributes
32
30
 
33
31
  define_method(:members) do
@@ -72,16 +70,23 @@ class Struct
72
70
  end
73
71
 
74
72
  def select(&block)
75
- @to_a.select(&block)
73
+ to_a.select(&block)
76
74
  end
77
75
 
78
76
  def eql?(other)
79
- instance_of?(other.class) && members.all? { |key| __send__(key).public_send(:eql?, other.__send__(key)) }
77
+ instance_of?(other.class) &&
78
+ members.all? { |key| self[key].eql?(other[key]) }
80
79
  end
81
80
 
82
81
  def ==(other)
83
82
  other = coerce(other).first if respond_to?(:coerce, true)
84
- other.kind_of?(self.class) && members.all? { |key| __send__(key).public_send(:==, other.__send__(key)) }
83
+ other.kind_of?(self.class) &&
84
+ members.all? { |key| self[key] == other[key] }
85
+ end
86
+
87
+ def hash
88
+ self.class.hash +
89
+ to_a.each_with_index.map {|value, i| (i+1) * value.hash}.sum
85
90
  end
86
91
 
87
92
  if keyword_init
@@ -117,11 +122,27 @@ class Struct
117
122
  end
118
123
  end
119
124
  end
120
- if class_name.nil?
121
- struct_class
122
- else
123
- const_set(class_name, struct_class)
125
+ end
126
+
127
+ ARG_VALIDATION = lambda do |class_name_or_attribute, *attributes|
128
+ class_name_or_attribute.nil? || attributes.any?(&:nil?)
129
+ end
130
+
131
+ CLASS_NAME_EXTRACTION = lambda do |class_name_or_attribute|
132
+ if class_name_or_attribute.is_a?(String)
133
+ raise NameError, "identifier name needs to be constant" unless class_name_or_attribute.match(/^[A-Z]/)
134
+ class_name_or_attribute
124
135
  end
125
136
  end
137
+
138
+ def new(class_name_or_attribute, *attributes, keyword_init: false)
139
+ raise 'Arguments cannot be nil' if ARG_VALIDATION[class_name_or_attribute, *attributes]
140
+ class_name = CLASS_NAME_EXTRACTION[class_name_or_attribute]
141
+ attributes.unshift(class_name_or_attribute) if class_name.nil?
142
+ attributes = attributes.map(&:to_sym)
143
+ struct_class = Class.new(&CLASS_DEFINITION_FOR_ATTRIBUTES[attributes, keyword_init])
144
+ class_name.nil? ? struct_class : const_set(class_name, struct_class)
145
+ end
146
+
126
147
  end
127
148
  end
@@ -46,15 +46,23 @@ module YASL
46
46
  class_for(structure['_class'])
47
47
  elsif YASL.json_basic_data_type?(structure) && !(structure.is_a?(String) && structure.start_with?('_'))
48
48
  structure
49
- elsif (structure['_class'] && (structure['_data'] || !structure.keys.detect {|key| key.start_with?('_') && key != '_class'} ))
49
+ elsif ruby_basic_data_type_structure?(structure)
50
50
  load_ruby_basic_data_type_object(structure['_class'], structure['_data'])
51
- elsif structure['_class'] && (structure['_id'] || structure['_instance_variables'] || structure['_class_variables'] || structure['_struct_member_values'])
51
+ elsif load_non_basic_data_type_structure?(structure)
52
52
  load_non_basic_data_type_object(structure)
53
53
  end
54
54
  end
55
55
 
56
56
  private
57
57
 
58
+ def ruby_basic_data_type_structure?(structure)
59
+ (structure['_class'] && (structure['_data'] || !structure.keys.detect {|key| key.start_with?('_') && key != '_class'} ))
60
+ end
61
+
62
+ def load_non_basic_data_type_structure?(structure)
63
+ structure['_class'] && (structure['_id'] || structure['_instance_variables'] || structure['_class_variables'] || structure['_struct_member_values'])
64
+ end
65
+
58
66
  def load_non_basic_data_type_object(structure, for_classes: false)
59
67
  class_name = structure['_class']
60
68
  object_class = class_for(class_name)
@@ -62,20 +70,11 @@ module YASL
62
70
  object = object_for_id(object_class, structure['_id'])
63
71
  if object.nil?
64
72
  object = for_classes ? object_class : object_class.new
65
- add_to_class_array(object, structure['_id']) if !object.is_a?(Class) && !object.is_a?(Module)
66
- end
67
- structure['_instance_variables'].to_a.each do |instance_var, value|
68
- value = load_structure(value)
69
- object.instance_variable_set("@#{instance_var}".to_sym, value)
70
- end
71
- structure['_struct_member_values'].to_a.each do |member, value|
72
- value = load_structure(value)
73
- object[member.to_sym] = value
74
- end
75
- structure['_class_variables'].to_a.each do |class_var, value|
76
- value = load_structure(value)
77
- object.class_variable_set("@@#{class_var}".to_sym, value)
73
+ add_to_class_array(object, structure['_id'])
78
74
  end
75
+ structure['_class_variables'].to_a.each { |class_var, value| object.class_variable_set("@@#{class_var}".to_sym, load_structure(value)) }
76
+ structure['_instance_variables'].to_a.each { |instance_var, value| object.instance_variable_set("@#{instance_var}".to_sym, load_structure(value)) }
77
+ structure['_struct_member_values'].to_a.each { |member, value| load_struct_member_value(object, member, value) }
79
78
  object
80
79
  ensure
81
80
  object_class&.define_method(:initialize, object_class.instance_method(:initialize_without_yasl))
@@ -106,9 +105,7 @@ module YASL
106
105
  when 'Array'
107
106
  data.map {|element| load_structure(element)}
108
107
  when 'Hash'
109
- data.reduce({}) do |new_hash, pair|
110
- new_hash.merge(load_structure(pair.first) => load_structure(pair.last))
111
- end
108
+ data.reduce({}) { |new_hash, pair| new_hash.merge(load_structure(pair.first) => load_structure(pair.last)) }
112
109
  end
113
110
  end
114
111
 
@@ -120,7 +117,7 @@ module YASL
120
117
  object_class = class_name_components.reduce(Object) do |result_class, class_name|
121
118
  result_class.const_get(class_name)
122
119
  end
123
- if !@whitelist_classes.include?(object_class)
120
+ if ![@whitelist_classes].compact.flatten.map(&:to_s).include?(object_class.to_s)
124
121
  raise "Class `#{class_name}` is not mentioned in `whitelist_classes` (e.g. `YASL.load(data, whitelist_classes: [#{class_name}])`)!"
125
122
  end
126
123
  object_class
@@ -133,10 +130,6 @@ module YASL
133
130
  structure && structure.is_a?(Hash) && structure['_class'] && structure['_id'].nil? && structure['_instance_variables'].nil? && structure['_class_variables'].nil? && structure['_struct_member_values'].nil? && structure['_data'].nil?
134
131
  end
135
132
 
136
- def add_to_classes(object)
137
- classes << object unless classes.include?(object)
138
- end
139
-
140
133
  def class_objects_for(object_class)
141
134
  class_objects[object_class] ||= {}
142
135
  end
@@ -148,11 +141,20 @@ module YASL
148
141
  end
149
142
 
150
143
  def add_to_class_array(object, class_object_id)
151
- return if class_object_id.nil?
144
+ return if object.is_a?(Class) || object.is_a?(Module) || class_object_id.nil?
152
145
  object_class = object.class
153
146
  found_object = object_for_id(object_class, class_object_id)
154
147
  class_objects_for(object_class)[class_object_id.to_i] = object unless found_object
155
148
  end
156
149
 
150
+ def load_struct_member_value(object, member, value)
151
+ value = load_structure(value)
152
+ begin
153
+ object[member.to_sym] = value
154
+ rescue => e
155
+ puts "#{e.message}. Setting `@#{member}` instance variable instead."
156
+ object.instance_variable_set("@#{member}".to_sym, value)
157
+ end
158
+ end
157
159
  end
158
160
  end
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: yasl 0.2.0 ruby lib
5
+ # stub: yasl 0.2.1 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "yasl".freeze
9
- s.version = "0.2.0"
9
+ s.version = "0.2.1"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Andy Maleh".freeze]
14
- s.date = "2020-12-31"
14
+ s.date = "2021-01-01"
15
15
  s.description = "A pure Ruby serialization library that works across different Ruby implementations like Opal and JRuby as an alternative to YAML/Marshal.".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yasl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-31 00:00:00.000000000 Z
11
+ date: 2021-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec