type_struct 0.5.0 → 0.6.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: cf544085d5325212144ddfb4119b2360807e1869
4
- data.tar.gz: 2c7efe99a85c7442ddc2fc5b304bb55ca9e78ac1
3
+ metadata.gz: 155aa214f6353b480dc87c69555db00a31623f7d
4
+ data.tar.gz: 31e28839d1fa9930ba9fddddf288f32dbeb02dee
5
5
  SHA512:
6
- metadata.gz: 6b6872ff8ea9c9bcb43c2743726a8458a7d0018a98f81191c511d03a700df971794de75f6d75ffbef9f8ff94e12230fb1483ede2c8cd66581ac7b94c97b4cfe5
7
- data.tar.gz: 93d5f28458f94944711361846df2ecdb9223ee87d8636444fb8cedba51a0068088679c53873c37a08f7c0b2d00118feda067eb2f38c565964c4cd9ff122a3362
6
+ metadata.gz: 56c32708f5d233891dbfeda2a403fffb04d2d8e7a674749a6f6f1c80eadae3f6ac868815299c608cb156585768d0aaffee01a8bff9590530fe7b4d99991885bc
7
+ data.tar.gz: dffe91d28e705eee66d507a3552d94d2a7ece6bd6d52be9d949643e50eebb3568a4cd7e9ee4c4363b5321b397c332728edb002310d758b810af674ab1a6265c1
data/README.md CHANGED
@@ -109,7 +109,9 @@ sample.str = 1 #=> TypeError
109
109
 
110
110
  ### Recursive Mapping
111
111
 
112
- Generate object from hash by recursive
112
+ Generate object from hash by recursive.
113
+
114
+ Like JSON package of golang and crystal-lang.
113
115
 
114
116
  ```ruby
115
117
  Point = TypeStruct.new(
@@ -0,0 +1,25 @@
1
+ class TypeStruct
2
+ UnionNotFoundError = Class.new(StandardError)
3
+
4
+ class MultiTypeError < StandardError
5
+ THIS_LIB_REGEXP = %r{lib/type_struct[./]}
6
+ PWD = Pathname.new(Dir.pwd)
7
+ attr_reader :errors
8
+ def initialize(errors)
9
+ @errors = errors
10
+ super("\n#{build_message}")
11
+ end
12
+
13
+ private
14
+
15
+ def build_message
16
+ @errors.map { |e|
17
+ b = e.backtrace_locations.find do |b|
18
+ b.absolute_path !~ THIS_LIB_REGEXP
19
+ end
20
+ relative_path = Pathname.new(b.absolute_path).relative_path_from(PWD)
21
+ "#{relative_path}:#{b.lineno}:in #{e.class} #{e}"
22
+ }.join("\n")
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  class TypeStruct
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/type_struct.rb CHANGED
@@ -1,34 +1,44 @@
1
+ require 'pathname'
2
+
1
3
  class TypeStruct
2
4
  require "type_struct/union"
3
5
  require "type_struct/array_of"
4
6
  require "type_struct/hash_of"
5
7
  require "type_struct/interface"
8
+ require "type_struct/exceptions"
6
9
  require "type_struct/version"
7
10
 
8
- UnionNotFoundError = Class.new(StandardError)
9
-
10
11
  def initialize(arg)
11
12
  sym_arg = {}
12
13
  arg.each do |k, v|
13
14
  sym_arg[k.to_sym] = v
14
15
  end
15
- self.class.members.each do |k|
16
- self[k] = sym_arg[k]
16
+ errors = []
17
+ klass = self.class
18
+ klass.members.each do |k|
19
+ unless klass.valid?(k, sym_arg[k])
20
+ begin
21
+ raise TypeError, "#{klass}##{k} expect #{klass.type(k)} got #{sym_arg[k].inspect}"
22
+ rescue TypeError => e
23
+ errors << e
24
+ end
25
+ end
26
+ instance_variable_set("@#{k}", sym_arg[k])
17
27
  end
28
+ raise MultiTypeError, errors unless errors.empty?
18
29
  end
19
30
 
20
31
  def ==(other)
21
32
  return false unless TypeStruct === other
22
- return false unless to_h == other.to_h
23
- true
33
+ to_h == other.to_h
24
34
  end
25
35
 
26
36
  def []=(k, v)
27
- __send__("#{k}=", v)
37
+ __send__ "#{k}=", v
28
38
  end
29
39
 
30
40
  def [](k)
31
- __send__(k)
41
+ __send__ k
32
42
  end
33
43
 
34
44
  def inspect
@@ -47,97 +57,117 @@ class TypeStruct
47
57
  m
48
58
  end
49
59
 
50
- class << self
51
- alias original_new new
52
- def new(**args, &block)
53
- c = Class.new(TypeStruct) do
54
- const_set :DEFINITION, args
60
+ module ClassMethods
61
+ def from_hash(h)
62
+ unless Hash === h
63
+ h = h.to_hash if h.respond_to?(:to_hash)
64
+ unless Hash === h
65
+ raise TypeError, "#{self}.from_hash only accept Hash got `#{h.class}'"
66
+ end
67
+ end
68
+ args = {}
69
+ errors = []
70
+ h.each do |key, value|
71
+ key = key.to_sym
72
+ t = type(key)
73
+ args[key] = try_convert(t, key, value, errors)
74
+ end
75
+ raise MultiTypeError, errors unless errors.empty?
76
+ new(args)
77
+ end
55
78
 
56
- class << self
57
- alias_method :new, :original_new
79
+ def definition
80
+ const_get(:DEFINITION)
81
+ end
58
82
 
59
- def from_hash(h)
60
- unless Hash === h
61
- h = h.to_hash if h.respond_to?(:to_hash)
62
- unless Hash === h
63
- raise TypeError, "#{self}.from_hash only accept Hash got `#{h.class}'"
64
- end
65
- end
66
- args = {}
67
- h.each do |key, value|
68
- key = key.to_sym
69
- t = type(key)
70
- args[key] = try_convert(t, key, value)
71
- end
72
- new(args)
73
- end
83
+ def members
84
+ definition.keys
85
+ end
74
86
 
75
- def definition
76
- const_get(:DEFINITION)
77
- end
87
+ def type(k)
88
+ definition[k]
89
+ end
78
90
 
79
- def members
80
- definition.keys
81
- end
91
+ def valid?(k, v)
92
+ definition[k] === v
93
+ end
82
94
 
83
- def type(k)
84
- definition[k]
95
+ private
96
+
97
+ def try_convert(klass, key, value, errors)
98
+ case klass
99
+ when Union
100
+ union_errors = []
101
+ klass.each do |k|
102
+ begin
103
+ return try_convert(k, key, value, nil)
104
+ rescue TypeError, MultiTypeError => e
105
+ union_errors << e
85
106
  end
107
+ end
86
108
 
87
- def valid?(k, v)
88
- definition[k] === v
109
+ raise UnionNotFoundError, "#{klass} is not found with value `#{value}'\nerrors:\n#{union_errors.join("\n")}"
110
+ when ArrayOf
111
+ unless Array === value
112
+ begin
113
+ raise TypeError, "#{self}##{key} expect #{klass.inspect} got #{value.inspect}"
114
+ rescue TypeError => e
115
+ raise unless errors
116
+ errors << e
89
117
  end
90
-
91
- private
92
-
93
- def try_convert(klass, key, value)
94
- return nil unless !klass.nil? && !value.nil?
95
-
96
- case klass
97
- when Union
98
- errors = []
99
- klass.each do |k|
100
- t = begin
101
- try_convert(k, key, value)
102
- rescue TypeError => e
103
- errors << e
104
- nil
105
- end
106
- return t if !t.nil?
107
- end
108
- raise UnionNotFoundError, "#{klass} is not found with value `#{value}'\nerrors:\n#{errors.join("\n")}"
109
- when ArrayOf
110
- unless Array === value
111
- raise TypeError, "#{self}##{key} expect #{klass.inspect} got #{value.inspect}"
112
- end
113
- value.map { |v| try_convert(klass.type, key, v) }
114
- when HashOf
115
- unless Hash === value
116
- raise TypeError, "#{self}##{key} expect #{klass.inspect} got #{value.inspect}"
117
- end
118
- new_hash = {}
119
- value.each do |hk, hv|
120
- new_hash[hk] = try_convert(klass.value_type, key, hv)
121
- end
122
- new_hash
123
- else
124
- if klass.respond_to?(:ancestors)
125
- if klass.ancestors.include?(TypeStruct)
126
- klass.from_hash(value)
127
- elsif klass.ancestors.include?(Struct)
128
- struct = klass.new
129
- value.each { |k, v| struct[k] = v }
130
- struct
131
- elsif klass === value
132
- value
133
- else
134
- raise TypeError, "#{self}##{key} expect #{klass.inspect} got #{value.inspect}"
135
- end
136
- else
137
- value
138
- end
118
+ return value
119
+ end
120
+ value.map { |v| try_convert(klass.type, key, v, errors) }
121
+ when HashOf
122
+ unless Hash === value
123
+ begin
124
+ raise TypeError, "#{self}##{key} expect #{klass.inspect} got #{value.inspect}"
125
+ rescue TypeError => e
126
+ raise unless errors
127
+ errors << e
128
+ end
129
+ return value
130
+ end
131
+ new_hash = {}
132
+ value.each do |hk, hv|
133
+ new_hash[hk] = try_convert(klass.value_type, key, hv, errors)
134
+ end
135
+ new_hash
136
+ else
137
+ if klass.respond_to?(:ancestors)
138
+ if klass.ancestors.include?(TypeStruct)
139
+ klass.from_hash(value)
140
+ elsif klass.ancestors.include?(Struct)
141
+ struct = klass.new
142
+ value.each { |k, v| struct[k] = v }
143
+ struct
144
+ elsif klass === value
145
+ value
146
+ else
147
+ begin
148
+ raise TypeError, "#{self}##{key} expect #{klass} got #{value.inspect}"
149
+ rescue => e
150
+ raise unless errors
151
+ errors << e
139
152
  end
153
+ value
140
154
  end
155
+ else
156
+ value
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ class << self
163
+ alias original_new new
164
+ def new(**args, &block)
165
+ c = Class.new(TypeStruct) do
166
+ extend ClassMethods
167
+ const_set :DEFINITION, args
168
+
169
+ class << self
170
+ alias_method :new, :original_new
141
171
  end
142
172
 
143
173
  args.each_key do |k|
@@ -153,6 +183,7 @@ class TypeStruct
153
183
  end
154
184
  end
155
185
  end
186
+
156
187
  if block_given?
157
188
  c.module_eval(&block)
158
189
  end
@@ -16,6 +16,40 @@ module TypeStructBenchmarkTest
16
16
  end
17
17
  end
18
18
 
19
+ def benchmark_struct_new(b)
20
+ i = 0
21
+ while i < b.n
22
+ Struct.new(:a, :b, :c)
23
+ i += 1
24
+ end
25
+ end
26
+
27
+ def benchmark_new_instance(b)
28
+ t = TypeStruct.new(
29
+ a: String,
30
+ b: Integer,
31
+ c: Regexp,
32
+ )
33
+ hash = { a: "aaa".freeze, b: 1, c: /abc/ }
34
+ i = 0
35
+ while i < b.n
36
+ t.new(hash)
37
+ i += 1
38
+ end
39
+ end
40
+
41
+ def benchmark_struct_new_instance(b)
42
+ t = Struct.new(:a, :b, :c)
43
+ a1 = "aaa".freeze
44
+ a2 = 1
45
+ a3 = /abc/
46
+ i = 0
47
+ while i < b.n
48
+ t.new(a1, a2, a3)
49
+ i += 1
50
+ end
51
+ end
52
+
19
53
  A = TypeStruct.new(
20
54
  a: Integer,
21
55
  )
@@ -34,8 +68,9 @@ module TypeStructBenchmarkTest
34
68
 
35
69
  def benchmark_from_hash(b)
36
70
  i = 0
71
+ hash = { e: { d: { c: { b: { a: 1 } } } } }
37
72
  while i < b.n
38
- E.from_hash(e: { d: { c: { b: { a: 1 } } } })
73
+ E.from_hash(hash)
39
74
  i += 1
40
75
  end
41
76
  end
@@ -89,7 +89,7 @@ module TypeStructTest
89
89
  hc.new(
90
90
  a: [],
91
91
  )
92
- rescue TypeError
92
+ rescue TypeStruct::MultiTypeError
93
93
  else
94
94
  t.error("TypeError was not railsed")
95
95
  end
@@ -103,7 +103,7 @@ module TypeStructTest
103
103
 
104
104
  begin
105
105
  hh = hc.from_hash(a: 1)
106
- rescue TypeError
106
+ rescue TypeStruct::MultiTypeError
107
107
  else
108
108
  t.error("TypeError dose not raise error")
109
109
  end
@@ -114,35 +114,27 @@ module TypeStructTest
114
114
  begin
115
115
  hsbn.from_hash(a: [])
116
116
  rescue TypeStruct::UnionNotFoundError
117
- rescue => e
118
- t.error("Unexpected error #{e.class}: #{e.message}")
119
117
  else
120
118
  t.error("Unexpected behavior")
121
119
  end
122
120
 
123
121
  begin
124
- hsbn.from_hash(a: {a: {b: 1.1}})
122
+ hsbn.from_hash(a: { a: { b: 1.1 } })
125
123
  rescue TypeStruct::UnionNotFoundError
126
- rescue => e
127
- t.error("Unexpected error #{e.class}: #{e.message}")
128
124
  else
129
125
  t.error("Unexpected behavior")
130
126
  end
131
127
 
132
128
  begin
133
- hsbn.from_hash(a: {"a" => {b: 1}})
134
- rescue TypeError
135
- rescue => e
136
- t.error("Unexpected error #{e.class}: #{e.message}")
129
+ hsbn.from_hash(a: { "a" => { b: 1 } })
130
+ rescue TypeStruct::MultiTypeError
137
131
  else
138
132
  t.error("Unexpected behavior")
139
133
  end
140
134
 
141
135
  begin
142
- hsbn.from_hash(a: {"a" => {b: 1.1}})
136
+ hsbn.from_hash(a: { "a" => { b: 1.1 } })
143
137
  rescue TypeStruct::UnionNotFoundError
144
- rescue => e
145
- t.error("Unexpected error #{e.class}: #{e.message}")
146
138
  else
147
139
  t.error("Unexpected behavior")
148
140
  end
@@ -165,9 +157,7 @@ module TypeStructTest
165
157
  a = TypeStruct.new(a: ArrayOf(Integer))
166
158
  begin
167
159
  a.from_hash(a: [1.1])
168
- rescue TypeError
169
- rescue => e
170
- t.error("Unexpected error #{e.class}")
160
+ rescue TypeStruct::MultiTypeError
171
161
  else
172
162
  t.error("Nothing raised TypeError")
173
163
  end
@@ -176,8 +166,6 @@ module TypeStructTest
176
166
  begin
177
167
  b.from_hash(a: [1.1])
178
168
  rescue TypeStruct::UnionNotFoundError
179
- rescue => e
180
- t.error("Unexpected error #{e.class}")
181
169
  else
182
170
  t.error("Nothing raised TypeStruct::UnionNotFoundError")
183
171
  end
@@ -185,10 +173,8 @@ module TypeStructTest
185
173
  c = TypeStruct.new(c: Integer)
186
174
  d = TypeStruct.new(d: ArrayOf(c) | NilClass)
187
175
  begin
188
- d.from_hash(d: [{c: 1.1}])
176
+ d.from_hash(d: [{ c: 1.1 }])
189
177
  rescue TypeStruct::UnionNotFoundError
190
- rescue => e
191
- t.error("Unexpected error #{e.class}")
192
178
  else
193
179
  t.error("Nothing raised TypeStruct::UnionNotFoundError")
194
180
  end
@@ -236,7 +222,7 @@ module TypeStructTest
236
222
 
237
223
  begin
238
224
  foo.from_hash(bar: { baz: nil })
239
- rescue TypeError
225
+ rescue TypeStruct::MultiTypeError
240
226
  else
241
227
  t.error("Bar.baz is not able to nil")
242
228
  end
@@ -255,7 +241,7 @@ module TypeStructTest
255
241
  end
256
242
 
257
243
  def o.to_hash
258
- {a: 1}
244
+ { a: 1 }
259
245
  end
260
246
  unless a === a.from_hash(o)
261
247
  t.error("Unexpected behavior")
@@ -263,30 +249,36 @@ module TypeStructTest
263
249
  end
264
250
 
265
251
  def test_s_from_hash_with_array_of(t)
266
- a = TypeStruct.new(a: ArrayOf(Integer))
252
+ a = TypeStruct.new(a: ArrayOf(Integer), b: Integer)
267
253
  begin
268
- a.from_hash(a: 1)
269
- rescue TypeError => e
270
- unless /#a expect ArrayOf\(Integer\) got 1/ =~ e.message
271
- t.error("message was changed: #{e.message}")
254
+ a.from_hash(a: 1, b: 'a')
255
+ rescue TypeStruct::MultiTypeError => e
256
+ [
257
+ /#a expect ArrayOf\(Integer\) got 1/,
258
+ /#b expect Integer got "a"/,
259
+ ].each do |expect|
260
+ unless expect =~ e.message
261
+ t.error("message was changed: #{e.message}")
262
+ end
272
263
  end
273
- rescue => e
274
- t.error("Unexpected error #{e}")
275
264
  else
276
265
  t.error("Unexpected behavior")
277
266
  end
278
267
  end
279
268
 
280
269
  def test_s_from_hash_with_hash_of(t)
281
- a = TypeStruct.new(a: HashOf(String, Integer))
270
+ a = TypeStruct.new(a: HashOf(String, Integer), b: Integer)
282
271
  begin
283
- a.from_hash(a: 1)
284
- rescue TypeError => e
285
- unless /#a expect HashOf\(String, Integer\) got 1/ =~ e.message
286
- t.error("message was changed: #{e.message}")
272
+ a.from_hash(a: 1, b: 'a')
273
+ rescue TypeStruct::MultiTypeError => e
274
+ [
275
+ /#a expect HashOf\(String, Integer\) got 1/,
276
+ /#b expect Integer got "a"/,
277
+ ].each do |expect|
278
+ unless expect =~ e.message
279
+ t.error("message was changed: #{e.message}")
280
+ end
287
281
  end
288
- rescue => e
289
- t.error("Unexpected error #{e}")
290
282
  else
291
283
  t.error("Unexpected behavior")
292
284
  end
@@ -296,7 +288,7 @@ module TypeStructTest
296
288
  a = TypeStruct.new(a: "a")
297
289
  begin
298
290
  a.from_hash(a: "b")
299
- rescue TypeError
291
+ rescue TypeStruct::MultiTypeError
300
292
  else
301
293
  t.error("Unexpected behavior")
302
294
  end
@@ -309,11 +301,7 @@ module TypeStructTest
309
301
  u = TypeStruct::Union.new(a, b, c)
310
302
  d = TypeStruct.new(d: u)
311
303
 
312
- begin
313
- d.from_hash(d: { b: 1 })
314
- rescue => e
315
- t.error("Unexpected error was raised #{e.class}: #{e.message}")
316
- end
304
+ d.from_hash(d: { b: 1 })
317
305
 
318
306
  begin
319
307
  d.from_hash(d: [b: 1])
@@ -516,6 +504,87 @@ module TypeStructTest
516
504
  end
517
505
  end
518
506
 
507
+ def test_multi_type_error(t)
508
+ a = TypeStruct.new(
509
+ a: Integer,
510
+ b: Integer,
511
+ c: Integer,
512
+ )
513
+ begin
514
+ a.new(
515
+ a: 'a',
516
+ b: 1,
517
+ c: '1',
518
+ )
519
+ rescue TypeStruct::MultiTypeError => err
520
+ unless err.errors.all? { |e| TypeError === e }
521
+ t.error("Empty errors")
522
+ end
523
+
524
+ [
525
+ /a expect Integer got "a"/,
526
+ /c expect Integer got "1"/,
527
+ ].each do |reg|
528
+ unless reg =~ err.message
529
+ t.error("should match error message #{reg} got #{err.message}")
530
+ end
531
+ end
532
+ rescue => err
533
+ raise err
534
+ else
535
+ t.error("Nothing raised an error")
536
+ end
537
+ end
538
+
539
+ def test_s_from_hash_multi_type_error(t)
540
+ a = TypeStruct.new(
541
+ a: Integer,
542
+ b: String,
543
+ c: ArrayOf(String),
544
+ )
545
+ b = TypeStruct.new(
546
+ b: a,
547
+ )
548
+ begin
549
+ b.from_hash(b: { a: '1', b: 1, c: /a/ })
550
+ rescue TypeStruct::MultiTypeError => err
551
+ unless err.errors.all? { |e| TypeError === e }
552
+ t.error("Empty errors")
553
+ end
554
+
555
+ [
556
+ /a expect Integer got "1"/,
557
+ /b expect String got 1/,
558
+ %r{c expect ArrayOf\(String\) got /a/},
559
+ ].each do |reg|
560
+ unless reg =~ err.message
561
+ t.error("should match error message #{reg} got #{err.message}")
562
+ end
563
+ end
564
+ end
565
+ end
566
+
567
+ def test_s_from_hash_multi_type_error_with_union(t)
568
+ a = TypeStruct.new(
569
+ a: ArrayOf(String) | NilClass,
570
+ )
571
+ b = TypeStruct.new(
572
+ b: a,
573
+ )
574
+ begin
575
+ b.from_hash(b: { a: '1' })
576
+ rescue TypeStruct::UnionNotFoundError => err
577
+ [
578
+ /a expect ArrayOf\(String\) got "1"/,
579
+ /a expect NilClass got "1"/,
580
+ ].each do |reg|
581
+ unless reg =~ err.message
582
+ t.error("should match error message #{reg} got #{err.message}")
583
+ end
584
+ end
585
+ end
586
+ end
587
+
519
588
  def test_getter(t)
520
589
  dummy = Dummy.new(str: "aaa", num: 123, reg: "abc", any: [1, "bbb"])
521
590
  _, err = go { dummy[:str] }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: type_struct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ksss
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-29 00:00:00.000000000 Z
11
+ date: 2016-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -68,6 +68,7 @@ files:
68
68
  - lib/type_struct.rb
69
69
  - lib/type_struct/array_of.rb
70
70
  - lib/type_struct/array_of_test.rb
71
+ - lib/type_struct/exceptions.rb
71
72
  - lib/type_struct/ext.rb
72
73
  - lib/type_struct/hash_of.rb
73
74
  - lib/type_struct/hash_of_test.rb
@@ -99,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
99
100
  version: '0'
100
101
  requirements: []
101
102
  rubyforge_project:
102
- rubygems_version: 2.6.1
103
+ rubygems_version: 2.6.2
103
104
  signing_key:
104
105
  specification_version: 4
105
106
  summary: Imitating static typed struct.