attributor 2.6.1 → 3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -29,9 +29,16 @@ module Attributor
29
29
  return collection.join(',')
30
30
  end
31
31
 
32
+ def self.describe(shallow=false, example: nil)
33
+ hash = super(shallow)
34
+ hash.delete(:member_attribute)
35
+ hash[:example] = example if example
36
+ hash
37
+ end
38
+
32
39
  def self.family
33
40
  Collection.family
34
41
  end
35
-
42
+
36
43
  end
37
44
  end
@@ -1,4 +1,20 @@
1
1
  module Attributor
2
+ class InvalidDefinition < StandardError
3
+ def initialize(type, cause)
4
+ type_name = if type.name
5
+ type.name
6
+ else
7
+ type.inspect
8
+ end
9
+
10
+ msg = "Structure definition for type #{type_name} is invalid. The following exception has occurred: #{cause.inspect}"
11
+ super(msg)
12
+ @cause = cause
13
+ end
14
+
15
+ attr_reader :cause
16
+ end
17
+
2
18
  class Hash
3
19
 
4
20
  MAX_EXAMPLE_DEPTH = 5
@@ -21,6 +37,8 @@ module Attributor
21
37
  @key_attribute = Attribute.new(@key_type)
22
38
  @value_attribute = Attribute.new(@value_type)
23
39
 
40
+ @error = false
41
+
24
42
 
25
43
  def self.key_type=(key_type)
26
44
  @key_type = Attributor.resolve_type(key_type)
@@ -37,7 +55,7 @@ module Attributor
37
55
  def self.family
38
56
  'hash'
39
57
  end
40
-
58
+
41
59
  @saved_blocks = []
42
60
  @options = {allow_extra: false}
43
61
  @keys = {}
@@ -54,14 +72,20 @@ module Attributor
54
72
  @value_type = v
55
73
  @key_attribute = Attribute.new(@key_type)
56
74
  @value_attribute = Attribute.new(@value_type)
75
+
76
+ @error = false
57
77
  end
58
78
  end
59
79
 
60
80
  def self.attributes(**options, &key_spec)
81
+ raise @error if @error
82
+
61
83
  self.keys(options, &key_spec)
62
84
  end
63
85
 
64
86
  def self.keys(**options, &key_spec)
87
+ raise @error if @error
88
+
65
89
  if block_given?
66
90
  @saved_blocks << key_spec
67
91
  @options.merge!(options)
@@ -86,8 +110,9 @@ module Attributor
86
110
  map[k.downcase] = k
87
111
  end
88
112
  end
89
-
90
- compiler
113
+ rescue => e
114
+ @error = InvalidDefinition.new(self, e)
115
+ raise
91
116
  end
92
117
 
93
118
  def self.dsl_class
@@ -134,10 +159,13 @@ module Attributor
134
159
 
135
160
 
136
161
  def self.example_contents(context, parent, **values)
162
+
137
163
  hash = ::Hash.new
138
164
  example_depth = context.size
139
165
 
140
166
  self.keys.each do |sub_attribute_name, sub_attribute|
167
+
168
+
141
169
  if sub_attribute.attributes
142
170
  # TODO: add option to raise an exception in this case?
143
171
  next if example_depth > MAX_EXAMPLE_DEPTH
@@ -148,8 +176,8 @@ module Attributor
148
176
  value = values.fetch(sub_attribute_name) do
149
177
  sub_attribute.example(sub_context, parent: parent)
150
178
  end
151
-
152
179
  sub_attribute.load(value,sub_context)
180
+
153
181
  end
154
182
 
155
183
 
@@ -160,6 +188,7 @@ module Attributor
160
188
  end
161
189
 
162
190
  def self.example(context=nil, **values)
191
+
163
192
  if (key_type == Object && value_type == Object && self.keys.empty?)
164
193
  return self.new
165
194
  end
@@ -339,7 +368,7 @@ module Attributor
339
368
  object.each do |k,v|
340
369
  next if k == self.extra_keys
341
370
 
342
- sub_context = self.generate_subcontext(Attributor::DEFAULT_ROOT_CONTEXT,k)
371
+ sub_context = self.generate_subcontext(context,k)
343
372
  hash.set(k, v, context: sub_context, recurse: recurse)
344
373
  end
345
374
 
@@ -365,19 +394,20 @@ module Attributor
365
394
  object.validate(context)
366
395
  end
367
396
 
368
- def self.describe(shallow=false)
369
- hash = super
397
+ def self.describe(shallow=false, example: nil)
398
+ hash = super(shallow)
370
399
 
371
400
  if key_type
372
- hash[:key] = {type: key_type.describe}
401
+ hash[:key] = {type: key_type.describe(true)}
373
402
  end
374
403
 
375
404
  if self.keys.any?
376
405
  # Spit keys if it's the root or if it's an anonymous structures
377
- if ( !shallow || self.name == nil) && self.keys.any?
406
+ if ( !shallow || self.name == nil)
378
407
  # FIXME: change to :keys when the praxis doc browser supports displaying those. or josep's demo is over.
379
- hash[:keys] = self.keys.each_with_object({}) do |(sub_name, sub_attribute), sub_attributes|
380
- sub_attributes[sub_name] = sub_attribute.describe(true)
408
+ hash[:attributes] = self.keys.each_with_object({}) do |(sub_name, sub_attribute), sub_attributes|
409
+ sub_example = example.get(sub_name) if example
410
+ sub_attributes[sub_name] = sub_attribute.describe(true, example: sub_example)
381
411
  end
382
412
  end
383
413
  else
@@ -436,6 +466,10 @@ module Attributor
436
466
  end
437
467
  end
438
468
 
469
+ def delete(key)
470
+ @contents.delete(key)
471
+ end
472
+
439
473
  attr_reader :validating, :dumping
440
474
 
441
475
  def initialize(contents={})
@@ -6,11 +6,17 @@ module Attributor
6
6
  undef :format
7
7
  undef :test rescue nil
8
8
 
9
+ if RUBY_ENGINE =~ /^jruby/
10
+ # We are "forced" to require it here (in case hasn't been yet) to make sure the added methods have been applied
11
+ require 'java'
12
+ # Only to then delete them, to make sure we don't have them clashing with any attributes
13
+ undef java, javax, org, com
14
+ end
15
+
9
16
  # Remove undesired methods inherited from Hash
10
17
  undef :size
11
18
  undef :keys
12
19
  undef :values
13
- undef :empty?
14
20
  undef :has_key?
15
21
 
16
22
  @key_type = Symbol
@@ -19,9 +25,10 @@ module Attributor
19
25
  @key_attribute = Attribute.new(@key_type)
20
26
  @value_attribute = Attribute.new(@value_type)
21
27
 
28
+
22
29
  def self.inherited(klass)
23
30
  k = self.key_type
24
- ka = self.key_attribute
31
+ ka = self.key_attribute
25
32
 
26
33
  v = self.value_type
27
34
  va = self.value_attribute
@@ -35,6 +42,8 @@ module Attributor
35
42
 
36
43
  @key_attribute = ka
37
44
  @value_attribute = va
45
+
46
+ @error = false
38
47
  end
39
48
  end
40
49
 
@@ -66,12 +75,6 @@ module Attributor
66
75
  end
67
76
  end
68
77
 
69
- def self.describe(shallow=false)
70
- hash = super
71
- hash[:attributes] = hash.delete :keys
72
- hash
73
- end
74
-
75
78
  def self.check_option!(name, value)
76
79
  case name
77
80
  when :identity
@@ -89,7 +92,7 @@ module Attributor
89
92
  end
90
93
 
91
94
  def self.generate_subcontext(context, subname)
92
- context + [subname]
95
+ context + [subname]
93
96
  end
94
97
 
95
98
  def self.example(context=nil, **values)
@@ -176,7 +179,7 @@ module Attributor
176
179
 
177
180
  self.attributes.each_with_object({}) do |(name, value), result|
178
181
  attribute = self.class.attributes[name]
179
-
182
+
180
183
  # skip dumping undefined attributes
181
184
  unless attribute
182
185
  warn "WARNING: Trying to dump unknown attribute: #{name.inspect} with context: #{context.inspect}"
@@ -18,16 +18,16 @@ module Attributor
18
18
 
19
19
  def self.example(context=nil, options:{})
20
20
  if options[:regexp]
21
- return options[:regexp].gen
21
+ # It may fail to generate an example, see bug #72.
22
+ options[:regexp].gen rescue ('Failed to generate example for %s' % options[:regexp].inspect)
22
23
  else
23
- return /\w+/.gen
24
+ /\w+/.gen
24
25
  end
25
26
  end
26
27
 
27
28
  def self.family
28
29
  'string'
29
30
  end
30
-
31
+
31
32
  end
32
-
33
33
  end
@@ -0,0 +1,57 @@
1
+ # Represents a href type.
2
+ require 'uri'
3
+
4
+ module Attributor
5
+ class URI
6
+ include Attributor::Type
7
+
8
+ def self.family
9
+ String.family
10
+ end
11
+
12
+ def self.valid_type?(value)
13
+ case value
14
+ when ::String, ::URI::Generic
15
+ true
16
+ else
17
+ false
18
+ end
19
+ end
20
+
21
+ def self.native_type
22
+ return ::URI::Generic
23
+ end
24
+
25
+ def self.example(context=nil, options={})
26
+ URI(/[:uri:]/.gen)
27
+ end
28
+
29
+ def self.load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
30
+ return nil if value.nil?
31
+ case value
32
+ when self.native_type
33
+ value
34
+ when ::String
35
+ URI(value)
36
+ else
37
+ raise CoercionError, context: context, from: value.class, to: self, value: value
38
+ end
39
+ end
40
+
41
+ def self.validate(value,context=Attributor::DEFAULT_ROOT_CONTEXT,attribute)
42
+ []
43
+ end
44
+
45
+ def check_option!(name, definition)
46
+ # No options are supported
47
+ :unknown
48
+ end
49
+
50
+ end
51
+ end
52
+
53
+ class Randgen
54
+ def self.uri
55
+ "http://example.com/#{word}/#{rand(10 ** 9)}"
56
+ end
57
+ end
@@ -1,3 +1,3 @@
1
1
  module Attributor
2
- VERSION = "2.6.1"
2
+ VERSION = "3.0"
3
3
  end
@@ -93,6 +93,48 @@ describe Attributor::Attribute do
93
93
  end
94
94
 
95
95
  end
96
+
97
+ context 'with an example' do
98
+
99
+ let(:attribute_options){ {} }
100
+ let(:example){ attribute.example }
101
+ subject(:described){ attribute.describe(false, example: example) }
102
+
103
+ context 'using a simple terminal type' do
104
+ let(:type) { String }
105
+ its(:keys){ should include(:example) }
106
+ it 'should have the passed example value' do
107
+ described.should have_key(:example)
108
+ described[:example].should eq(example)
109
+ end
110
+ it 'should have removed the example from the :type' do
111
+ described[:type].should_not have_key(:example)
112
+ end
113
+
114
+ end
115
+
116
+ context 'using a complex type' do
117
+ let(:type) { Cormorant }
118
+ its(:keys){ should_not include(:example) }
119
+
120
+ it 'Should see examples in the right places, depending on leaf/no-leaf types' do
121
+ # String, a leaf attribute type: should have example
122
+ name_attr = described[:type][:attributes][:name]
123
+ name_attr.should include(:example)
124
+ name_attr[:type].should_not include(:example)
125
+
126
+ # Struct, a non-leaf attribute type: shouldn't have example
127
+ ts_attr = described[:type][:attributes][:timestamps]
128
+ ts_attr.should_not include(:example)
129
+ ts_attr[:type].should_not include(:example)
130
+
131
+ # DateTime inside a Struct, a nested leaf attribute type: should have example
132
+ born_attr = ts_attr[:type][:attributes][:born_at]
133
+ born_attr.should include(:example)
134
+ born_attr[:type].should_not include(:example)
135
+ end
136
+ end
137
+ end
96
138
  end
97
139
 
98
140
 
@@ -135,10 +177,8 @@ describe Attributor::Attribute do
135
177
  end
136
178
  end
137
179
 
138
-
139
180
  context 'example' do
140
181
  let(:example) { nil }
141
- let(:attribute_options) { {:example => example} }
142
182
 
143
183
  context 'with nothing specified' do
144
184
  let(:attribute_options) { {} }
@@ -151,108 +191,141 @@ describe Attributor::Attribute do
151
191
  end
152
192
  end
153
193
 
194
+ context 'with an attribute that has the values option set' do
195
+ let(:values) { ["one", "two"] }
196
+ let(:attribute_options) { {:values => values} }
197
+ it 'picks a random value' do
198
+ values.should include subject.example
199
+ end
200
+
201
+ end
202
+
203
+ context 'deterministic examples' do
204
+ let(:example) { /\w+/ }
205
+ let(:attribute_options) { {:example => example} }
206
+
207
+ it 'can take a context to pre-seed the random number generator' do
208
+ example_1 = subject.example(['context'])
209
+ example_2 = subject.example(['context'])
210
+
211
+ example_1.should eq example_2
212
+ end
213
+
214
+ it 'can take a context to pre-seed the random number generator' do
215
+ example_1 = subject.example(['context'])
216
+ example_2 = subject.example(['different context'])
217
+
218
+ example_1.should_not eq example_2
219
+ end
220
+ end
221
+
222
+ context 'with an example option' do
223
+ let(:example){ "Bob" }
224
+ let(:attribute_options) { {example: example , regexp: /Bob/ } }
225
+
226
+ its(:example){ should == example }
227
+
228
+ context 'that is not valid' do
229
+ let(:example){ "Frank" }
230
+ it 'raises a validation error' do
231
+ expect{
232
+ subject.example
233
+ }.to raise_error(Attributor::AttributorException, /Error generating example/)
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ context 'example_from_options' do
240
+ let(:example) { nil }
241
+ let(:generated_example) { example }
242
+ let(:attribute_options) { {:example => example} }
243
+ let(:parent){ nil }
244
+ let(:context){ Attributor::DEFAULT_ROOT_CONTEXT}
245
+
246
+ subject(:example_result) { attribute.example_from_options( parent, context ) }
247
+ before do
248
+ attribute.should_receive(:load).with( generated_example , an_instance_of(Array) ).and_call_original
249
+ end
154
250
 
155
251
  context 'with a string' do
156
252
  let(:example) { "example" }
157
253
 
158
- its(:example) { should be example }
254
+ it { should be example }
255
+ end
256
+
257
+ context 'with an integer' do
258
+ let(:type) { Attributor::Integer }
259
+ let(:example) { 5 }
260
+ it { should be example }
159
261
  end
160
262
 
161
263
  context 'with a regexp' do
162
264
  let(:example) { /\w+/ }
163
-
265
+ let(:generated_example) { /\w+/.gen }
164
266
 
165
267
  it 'calls #gen on the regexp' do
166
- example.should_receive(:gen).and_call_original
167
- subject.example.should =~ example
268
+ example.should_receive(:gen).and_return(generated_example)
269
+
270
+ example_result.should =~ example
168
271
  end
169
272
 
170
273
  context 'for a type with a non-String native_type' do
171
274
  let(:type) { Attributor::Integer }
172
- context 'using a regexp' do
173
- let(:example) { /\d{5}/ }
174
- it 'coerces the example value properly' do
175
- example.should_receive(:gen).and_call_original
176
- type.should_receive(:load).and_call_original
177
-
178
- subject.example.should be_kind_of(type.native_type)
179
- end
180
- end
181
- context 'usign a native Integer type' do
182
- let(:example) { 5 }
183
- it 'coerces the example value properly' do
184
- type.should_receive(:load).and_call_original
185
- subject.example.should be_kind_of(type.native_type)
186
- end
275
+ let(:example) { /\d{5}/ }
276
+ let(:generated_example) { /\d{5}/.gen }
277
+
278
+ it 'coerces the example value properly' do
279
+ example.should_receive(:gen).and_return(generated_example)
280
+ type.should_receive(:load).and_call_original
281
+
282
+ example_result.should be_kind_of(type.native_type)
187
283
  end
188
284
  end
189
- end
190
285
 
286
+ end
191
287
 
192
288
  context 'with a proc' do
289
+ let(:parent){ Object.new }
290
+
193
291
  context 'with one argument' do
194
292
  let(:example) { lambda { |obj| 'ok' } }
195
- let(:some_object) { Object.new }
293
+ let(:generated_example) { 'ok' }
196
294
 
197
295
  before do
198
- example.should_receive(:call).with(some_object).and_call_original
296
+ example.should_receive(:call).with(parent).and_return(generated_example)
199
297
  end
200
298
 
201
299
  it 'passes any given parent through to the example proc' do
202
- subject.example(nil, parent: some_object).should == 'ok'
300
+ example_result.should == 'ok'
203
301
  end
204
302
  end
205
303
 
206
304
  context 'with two arguments' do
207
305
  let(:example) { lambda { |obj, context| "#{context} ok" } }
208
- let(:some_object) { Object.new }
209
- let(:some_context) { ['some_context'] }
210
-
306
+ let(:generated_example) { "#{context} ok" }
307
+ let(:context){ ['some_context'] }
211
308
  before do
212
- example.should_receive(:call).with(some_object, some_context).and_call_original
309
+ example.should_receive(:call).with(parent, context).and_return(generated_example)
213
310
  end
214
311
 
215
312
  it 'passes any given parent through to the example proc' do
216
- subject.example(some_context, parent: some_object).should == "#{some_context} ok"
313
+ example_result.should == "#{context} ok"
217
314
  end
218
315
  end
219
316
 
220
317
  end
221
318
 
222
319
  context 'with an array' do
223
- let(:example) { ["one", "two"] }
224
- it 'picks a random value' do
225
- example.should include subject.example
226
- end
227
- end
228
-
229
- context 'with an attribute that has the values option set' do
230
- let(:values) { ["one", "two"] }
231
- let(:attribute_options) { {:values => values} }
320
+ let(:example) { ["one"] }
321
+ let(:generated_example) { "one" }
232
322
  it 'picks a random value' do
233
- values.should include subject.example
323
+ example.should_receive(:pick).and_call_original
324
+ example.should include example_result
234
325
  end
235
326
 
236
327
  end
237
328
 
238
- context 'deterministic examples' do
239
- let(:example) { /\w+/ }
240
- it 'can take a context to pre-seed the random number generator' do
241
- example_1 = subject.example(['context'])
242
- example_2 = subject.example(['context'])
243
-
244
- example_1.should eq example_2
245
- end
246
-
247
- it 'can take a context to pre-seed the random number generator' do
248
- example_1 = subject.example(['context'])
249
- example_2 = subject.example(['different context'])
250
-
251
- example_1.should_not eq example_2
252
- end
253
-
254
-
255
- end
256
329
  end
257
330
 
258
331
  context 'load' do
@@ -286,7 +359,7 @@ describe Attributor::Attribute do
286
359
  it { should == default_value}
287
360
 
288
361
  end
289
-
362
+
290
363
  context 'for a Proc-based default value' do
291
364
  let(:context){ ["$"] }
292
365
  subject(:result){ attribute.load(value,context) }
@@ -296,21 +369,21 @@ describe Attributor::Attribute do
296
369
  let(:default_value) { proc { "no_params" } }
297
370
  it { should == default_value.call }
298
371
  end
299
-
372
+
300
373
  context 'with 1 argument (the parent)' do
301
374
  let(:default_value) { proc {|parent| "parent is fake: #{parent.class}" } }
302
375
  it { should == "parent is fake: Attributor::FakeParent" }
303
376
  end
304
-
377
+
305
378
  context 'with 2 argument (the parent and the contents)' do
306
379
  let(:default_value) { proc {|parent,context| "parent is fake: #{parent.class} and context is: #{context}" } }
307
380
  it { should == "parent is fake: Attributor::FakeParent and context is: [\"$\"]"}
308
381
  end
309
-
382
+
310
383
  context 'which attempts to use the parent (which is not supported for the moment)' do
311
384
  let(:default_value) { proc {|parent| "any parent method should spit out warning: [#{parent.something}]" } }
312
385
  it "should output a warning" do
313
- begin
386
+ begin
314
387
  old_verbose, $VERBOSE = $VERBOSE, nil
315
388
  Kernel.should_receive(:warn).and_call_original
316
389
  attribute.load(value,context).should == "any parent method should spit out warning: []"
@@ -638,7 +711,6 @@ describe Attributor::Attribute do
638
711
  let(:type) { Attributor::Collection.of(member_type) }
639
712
  let(:member_options) { {} }
640
713
 
641
-
642
714
  context 'the member_attribute of that type' do
643
715
  subject(:member_attribute) { attribute.type.member_attribute }
644
716
  it { should be_kind_of(Attributor::Attribute)}