attributor 2.6.1 → 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.
@@ -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)}