attributor 5.5 → 6.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 +5 -5
- data/CHANGELOG.md +17 -0
- data/lib/attributor/attribute.rb +67 -79
- data/lib/attributor/hash_dsl_compiler.rb +6 -5
- data/lib/attributor/type.rb +3 -0
- data/lib/attributor/types/collection.rb +4 -1
- data/lib/attributor/types/hash.rb +24 -16
- data/lib/attributor/types/model.rb +9 -21
- data/lib/attributor/version.rb +1 -1
- data/lib/attributor.rb +2 -6
- data/spec/attribute_spec.rb +114 -121
- data/spec/hash_dsl_compiler_spec.rb +5 -5
- data/spec/spec_helper.rb +0 -2
- data/spec/support/models.rb +7 -7
- data/spec/types/hash_spec.rb +63 -22
- data/spec/types/model_spec.rb +7 -1
- metadata +3 -7
- data/lib/attributor/attribute_resolver.rb +0 -111
- data/spec/attribute_resolver_spec.rb +0 -237
data/spec/attribute_spec.rb
CHANGED
@@ -11,7 +11,7 @@ describe Attributor::Attribute do
|
|
11
11
|
|
12
12
|
context 'initialize' do
|
13
13
|
its(:type) { should be type }
|
14
|
-
its(:options) { should
|
14
|
+
its(:options) { should eq attribute_options }
|
15
15
|
|
16
16
|
it 'calls check_options!' do
|
17
17
|
expect_any_instance_of(Attributor::Attribute).to receive(:check_options!)
|
@@ -62,6 +62,7 @@ describe Attributor::Attribute do
|
|
62
62
|
it 'as well as the type-specific ones' do
|
63
63
|
expect(js[:type]).to eq(:integer)
|
64
64
|
end
|
65
|
+
|
65
66
|
end
|
66
67
|
|
67
68
|
end
|
@@ -71,8 +72,8 @@ describe Attributor::Attribute do
|
|
71
72
|
let(:expected) do
|
72
73
|
h = {type: {name: 'String', id: type.id, family: type.family}}
|
73
74
|
common = attribute_options.select{|k,v| Attributor::Attribute::TOP_LEVEL_OPTIONS.include? k }
|
74
|
-
h.merge!(
|
75
|
-
h[:options] = {:
|
75
|
+
h.merge!(common)
|
76
|
+
h[:options] = {min: 0}
|
76
77
|
h
|
77
78
|
end
|
78
79
|
|
@@ -194,6 +195,49 @@ describe Attributor::Attribute do
|
|
194
195
|
end.not_to raise_error
|
195
196
|
end
|
196
197
|
end
|
198
|
+
|
199
|
+
context 'custom_options' do
|
200
|
+
let(:option_name) { :foo }
|
201
|
+
let(:custom_option_args) { [option_name, String] }
|
202
|
+
|
203
|
+
around do |example|
|
204
|
+
Attributor::Attribute.custom_option *custom_option_args
|
205
|
+
example.run
|
206
|
+
Attributor::Attribute.custom_options.delete option_name
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'raises ArgumentError if given an existing option' do
|
210
|
+
expect {
|
211
|
+
Attributor::Attribute.custom_option :default, Object
|
212
|
+
}.to raise_error(ArgumentError)
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'accepts custom options' do
|
216
|
+
expect do
|
217
|
+
Attributor::Attribute.new(Integer, foo: 'unvalidated')
|
218
|
+
end.not_to raise_error
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'can validate the custom option value' do
|
222
|
+
let(:custom_option_args) { [option_name, String, values: ['valid']] }
|
223
|
+
it 'does not raise with a valid option value' do
|
224
|
+
expect do
|
225
|
+
Attributor::Attribute.new(Integer, foo: 'valid')
|
226
|
+
end.not_to raise_error
|
227
|
+
end
|
228
|
+
it 'raises with an invalid option value' do
|
229
|
+
expect do
|
230
|
+
Attributor::Attribute.new(Integer, foo: 'invalid')
|
231
|
+
end.to raise_error(Attributor::AttributorException)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'appear in as_json_schema' do
|
236
|
+
attribute = Attributor::Attribute.new(Integer, foo: 'valid')
|
237
|
+
json_schema = attribute.as_json_schema
|
238
|
+
expect(json_schema[:'x-foo']).to eq 'valid'
|
239
|
+
end
|
240
|
+
end
|
197
241
|
end
|
198
242
|
|
199
243
|
context 'example' do
|
@@ -414,10 +458,49 @@ describe Attributor::Attribute do
|
|
414
458
|
context 'applying attribute options' do
|
415
459
|
context ':required' do
|
416
460
|
let(:attribute_options) { { required: true } }
|
461
|
+
context 'has no effect on a bare attribute' do
|
462
|
+
let(:value) { 'val' }
|
463
|
+
it 'it does not error, as we do not know if the parent attribute key was passed in (done at the Hash level)' do
|
464
|
+
expect(attribute.validate(value, context)).to be_empty
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
context ':null false (non-nullable)' do
|
469
|
+
let(:attribute_options) { { null: false } }
|
470
|
+
context 'with a nil value' do
|
471
|
+
let(:value) { nil }
|
472
|
+
it 'returns an error' do
|
473
|
+
expect(attribute.validate(value, context).first).to eq 'Attribute context is not nullable'
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
context ':null true (nullable)' do
|
478
|
+
let(:attribute_options) { { null: true } }
|
479
|
+
context 'with a nil value' do
|
480
|
+
let(:value) { nil }
|
481
|
+
it 'does not error' do
|
482
|
+
expect(attribute.validate(value, context)).to be_empty
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
context 'defaults to non-nullable if null not defined' do
|
487
|
+
let(:attribute_options) { { } }
|
417
488
|
context 'with a nil value' do
|
418
489
|
let(:value) { nil }
|
419
490
|
it 'returns an error' do
|
420
|
-
expect(
|
491
|
+
expect(Attributor::Attribute.default_for_null).to be(false)
|
492
|
+
expect(attribute.validate(value, context).first).to eq 'Attribute context is not nullable'
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
context 'default can be overrideable with true' do
|
498
|
+
let(:attribute_options) { { } }
|
499
|
+
context 'with a nil value' do
|
500
|
+
let(:value) { nil }
|
501
|
+
it 'suceeds' do
|
502
|
+
expect(Attributor::Attribute).to receive(:default_for_null).and_return(true)
|
503
|
+
expect(attribute.validate(value, context)).to be_empty
|
421
504
|
end
|
422
505
|
end
|
423
506
|
end
|
@@ -463,6 +546,13 @@ describe Attributor::Attribute do
|
|
463
546
|
end
|
464
547
|
end
|
465
548
|
|
549
|
+
context 'with a nil value' do
|
550
|
+
let(:value) { nil }
|
551
|
+
it 'returns no errors' do
|
552
|
+
expect(errors).to be_empty
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
466
556
|
context 'with a value of a value different than the native_type' do
|
467
557
|
let(:value) { 1 }
|
468
558
|
|
@@ -472,58 +562,6 @@ describe Attributor::Attribute do
|
|
472
562
|
end
|
473
563
|
end
|
474
564
|
end
|
475
|
-
|
476
|
-
context '#validate_missing_value' do
|
477
|
-
let(:key) { '$.instance.ssh_key.name' }
|
478
|
-
let(:value) { /\w+/.gen }
|
479
|
-
|
480
|
-
let(:attribute_options) { { required_if: key } }
|
481
|
-
|
482
|
-
let(:ssh_key) { double('ssh_key', name: value) }
|
483
|
-
let(:instance) { double('instance', ssh_key: ssh_key) }
|
484
|
-
|
485
|
-
before { Attributor::AttributeResolver.current.register('instance', instance) }
|
486
|
-
|
487
|
-
let(:attribute_context) { ['$', 'params', 'key_material'] }
|
488
|
-
subject(:errors) { attribute.validate_missing_value(attribute_context) }
|
489
|
-
|
490
|
-
context 'for a simple dependency without a predicate' do
|
491
|
-
context 'that is satisfied' do
|
492
|
-
it { should_not be_empty }
|
493
|
-
end
|
494
|
-
|
495
|
-
context 'that is missing' do
|
496
|
-
let(:value) { nil }
|
497
|
-
it { should be_empty }
|
498
|
-
end
|
499
|
-
end
|
500
|
-
|
501
|
-
context 'with a dependency that has a predicate' do
|
502
|
-
let(:value) { 'default_ssh_key_name' }
|
503
|
-
# subject(:errors) { attribute.validate_missing_value('') }
|
504
|
-
|
505
|
-
context 'where the target attribute exists, and matches the predicate' do
|
506
|
-
let(:attribute_options) { { required_if: { key => /default/ } } }
|
507
|
-
|
508
|
-
it { should_not be_empty }
|
509
|
-
|
510
|
-
its(:first) { should match(/Attribute #{Regexp.quote(Attributor.humanize_context(attribute_context))} is required when #{Regexp.quote(key)} matches/) }
|
511
|
-
end
|
512
|
-
|
513
|
-
context 'where the target attribute exists, but does not match the predicate' do
|
514
|
-
let(:attribute_options) { { required_if: { key => /other/ } } }
|
515
|
-
|
516
|
-
it { should be_empty }
|
517
|
-
end
|
518
|
-
|
519
|
-
context 'where the target attribute does not exist' do
|
520
|
-
let(:attribute_options) { { required_if: { key => /default/ } } }
|
521
|
-
let(:ssh_key) { double('ssh_key', name: nil) }
|
522
|
-
|
523
|
-
it { should be_empty }
|
524
|
-
end
|
525
|
-
end
|
526
|
-
end
|
527
565
|
end
|
528
566
|
|
529
567
|
context 'for an attribute for a subclass of Model' do
|
@@ -592,71 +630,6 @@ describe Attributor::Attribute do
|
|
592
630
|
end
|
593
631
|
end
|
594
632
|
end
|
595
|
-
|
596
|
-
context '#validate_missing_value' do
|
597
|
-
let(:type) { Duck }
|
598
|
-
let(:attribute_name) { nil }
|
599
|
-
let(:attribute) { Duck.attributes[attribute_name] }
|
600
|
-
|
601
|
-
let(:attribute_context) { ['$', 'duck', attribute_name.to_s] }
|
602
|
-
subject(:errors) { attribute.validate_missing_value(attribute_context) }
|
603
|
-
|
604
|
-
before do
|
605
|
-
Attributor::AttributeResolver.current.register('duck', duck)
|
606
|
-
end
|
607
|
-
|
608
|
-
context 'for a dependency with no predicate' do
|
609
|
-
let(:attribute_name) { :email }
|
610
|
-
|
611
|
-
let(:duck) do
|
612
|
-
d = Duck.new
|
613
|
-
d.age = 1
|
614
|
-
d.name = 'Donald'
|
615
|
-
d
|
616
|
-
end
|
617
|
-
|
618
|
-
context 'where the target attribute exists, and matches the predicate' do
|
619
|
-
it { should_not be_empty }
|
620
|
-
its(:first) { should eq 'Attribute $.duck.email is required when name (for $.duck) is present.' }
|
621
|
-
end
|
622
|
-
context 'where the target attribute does not exist' do
|
623
|
-
before do
|
624
|
-
duck.name = nil
|
625
|
-
end
|
626
|
-
it { should be_empty }
|
627
|
-
end
|
628
|
-
end
|
629
|
-
|
630
|
-
context 'for a dependency with a predicate' do
|
631
|
-
let(:attribute_name) { :age }
|
632
|
-
|
633
|
-
let(:duck) do
|
634
|
-
d = Duck.new
|
635
|
-
d.name = 'Daffy'
|
636
|
-
d.email = 'daffy@darkwing.uoregon.edu' # he's a duck,get it?
|
637
|
-
d
|
638
|
-
end
|
639
|
-
|
640
|
-
context 'where the target attribute exists, and matches the predicate' do
|
641
|
-
it { should_not be_empty }
|
642
|
-
its(:first) { should match(/Attribute #{Regexp.quote('$.duck.age')} is required when name #{Regexp.quote('(for $.duck)')} matches/) }
|
643
|
-
end
|
644
|
-
|
645
|
-
context 'where the target attribute exists, and does not match the predicate' do
|
646
|
-
before do
|
647
|
-
duck.name = 'Donald'
|
648
|
-
end
|
649
|
-
it { should be_empty }
|
650
|
-
end
|
651
|
-
|
652
|
-
context 'where the target attribute does not exist' do
|
653
|
-
before do
|
654
|
-
duck.name = nil
|
655
|
-
end
|
656
|
-
it { should be_empty }
|
657
|
-
end
|
658
|
-
end
|
659
|
-
end
|
660
633
|
end
|
661
634
|
end
|
662
635
|
|
@@ -713,4 +686,24 @@ describe Attributor::Attribute do
|
|
713
686
|
end
|
714
687
|
end
|
715
688
|
end
|
689
|
+
|
690
|
+
context '.nullable_attribute?' do
|
691
|
+
subject { described_class.nullable_attribute?(options) }
|
692
|
+
context 'with null: true option' do
|
693
|
+
let(:options) { { null: true } }
|
694
|
+
it { should be_truthy }
|
695
|
+
end
|
696
|
+
context 'with null: false option' do
|
697
|
+
let(:options) { { null: false } }
|
698
|
+
it { should be_falsey }
|
699
|
+
end
|
700
|
+
context 'defaults to false without any null option' do
|
701
|
+
let(:options) { { } }
|
702
|
+
it { should be_falsey }
|
703
|
+
end
|
704
|
+
context 'defaults to false if null: nil' do
|
705
|
+
let(:options) { { null: nil } }
|
706
|
+
it { should be_falsey }
|
707
|
+
end
|
708
|
+
end
|
716
709
|
end
|
@@ -109,31 +109,31 @@ describe Attributor::HashDSLCompiler do
|
|
109
109
|
context 'for :all' do
|
110
110
|
let(:arguments) { { all: [:one, :two, :three] } }
|
111
111
|
let(:value) { [:one] }
|
112
|
-
let(:validation_error) { [
|
112
|
+
let(:validation_error) { ["Attribute $.key(:two) is required.", "Attribute $.key(:three) is required."] }
|
113
113
|
it { expect(subject).to include(*validation_error) }
|
114
114
|
end
|
115
115
|
context 'for :exactly' do
|
116
116
|
let(:requirement) { req_class.new(exactly: 1).of(:one, :two) }
|
117
117
|
let(:value) { [:one, :two] }
|
118
|
-
let(:validation_error) { 'Exactly 1 of the following
|
118
|
+
let(:validation_error) { 'Exactly 1 of the following attributes [:one, :two] are required for $. Found 2 instead: [:one, :two]' }
|
119
119
|
it { expect(subject).to include(validation_error) }
|
120
120
|
end
|
121
121
|
context 'for :at_least' do
|
122
122
|
let(:requirement) { req_class.new(at_least: 2).of(:one, :two, :three) }
|
123
123
|
let(:value) { [:one] }
|
124
|
-
let(:validation_error) { 'At least 2
|
124
|
+
let(:validation_error) { 'At least 2 attributes out of [:one, :two, :three] are required to be passed in for $. Found [:one]' }
|
125
125
|
it { expect(subject).to include(validation_error) }
|
126
126
|
end
|
127
127
|
context 'for :at_most' do
|
128
128
|
let(:requirement) { req_class.new(at_most: 1).of(:one, :two, :three) }
|
129
129
|
let(:value) { [:one, :two] }
|
130
|
-
let(:validation_error) { 'At most 1
|
130
|
+
let(:validation_error) { 'At most 1 attributes out of [:one, :two, :three] can be passed in for $. Found [:one, :two]' }
|
131
131
|
it { expect(subject).to include(validation_error) }
|
132
132
|
end
|
133
133
|
context 'for :exclusive' do
|
134
134
|
let(:arguments) { { exclusive: [:one, :two] } }
|
135
135
|
let(:value) { [:one, :two] }
|
136
|
-
let(:validation_error) { '
|
136
|
+
let(:validation_error) { 'Attributes [:one, :two] are mutually exclusive for $.' }
|
137
137
|
it { expect(subject).to include(validation_error) }
|
138
138
|
end
|
139
139
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -23,9 +23,7 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
|
23
23
|
|
24
24
|
RSpec.configure do |config|
|
25
25
|
config.around(:each) do |example|
|
26
|
-
Attributor::AttributeResolver.current = Attributor::AttributeResolver.new
|
27
26
|
example.run
|
28
|
-
Attributor::AttributeResolver.current = nil
|
29
27
|
end
|
30
28
|
end
|
31
29
|
|
data/spec/support/models.rb
CHANGED
@@ -11,9 +11,9 @@ end
|
|
11
11
|
|
12
12
|
class Duck < Attributor::Model
|
13
13
|
attributes do
|
14
|
-
attribute :age, Attributor::Integer
|
14
|
+
attribute :age, Attributor::Integer
|
15
15
|
attribute :name, Attributor::String
|
16
|
-
attribute :email, Attributor::String
|
16
|
+
attribute :email, Attributor::String
|
17
17
|
attribute :angry, Attributor::Boolean, default: true, example: /true|false/, description: 'Angry bird?'
|
18
18
|
attribute :weight, Attributor::Float, example: /\d{1,2}\.\d/, description: 'The weight of the duck'
|
19
19
|
attribute :type, Attributor::Symbol, values: [:duck]
|
@@ -50,15 +50,15 @@ class Cormorant < Attributor::Model
|
|
50
50
|
end
|
51
51
|
|
52
52
|
# This will be a collection of arbitrary Ruby Objects
|
53
|
-
attribute :
|
53
|
+
attribute :all_the_fish, Attributor::Collection, description: 'All kinds of fish for feeding the babies'
|
54
54
|
|
55
55
|
# This will be a collection of Cormorants (note, this relationship is circular)
|
56
|
-
attribute :neighbors, Attributor::Collection.of(Cormorant), description: 'Neighbor cormorants'
|
56
|
+
attribute :neighbors, Attributor::Collection.of(Cormorant), member_options: {null: false}, description: 'Neighbor cormorants', null: false
|
57
57
|
|
58
58
|
# This will be a collection of instances of an anonymous Struct class, each having two well-defined attributes
|
59
59
|
|
60
60
|
attribute :babies, Attributor::Collection.of(Attributor::Struct), description: 'All the babies', member_options: { identity: :name } do
|
61
|
-
attribute :name, Attributor::String, example: /[:name]/, description: 'The name of the baby cormorant'
|
61
|
+
attribute :name, Attributor::String, example: /[:name]/, description: 'The name of the baby cormorant', required: true
|
62
62
|
attribute :months, Attributor::Integer, default: 0, min: 0, description: 'The age in months of the baby cormorant'
|
63
63
|
attribute :weight, Attributor::Float, example: /\d{1,2}\.\d{3}/, description: 'The weight in kg of the baby cormorant'
|
64
64
|
end
|
@@ -76,8 +76,8 @@ end
|
|
76
76
|
|
77
77
|
class Address < Attributor::Model
|
78
78
|
attributes do
|
79
|
-
attribute :name, String, example: /\w
|
80
|
-
attribute :state, String, values: %w(OR CA)
|
79
|
+
attribute :name, String, example: /\w+/, null: true
|
80
|
+
attribute :state, String, values: %w(OR CA), null: false
|
81
81
|
attribute :person, Person, example: proc { |address, context| Person.example(context, address: address) }
|
82
82
|
requires :name
|
83
83
|
end
|
data/spec/types/hash_spec.rb
CHANGED
@@ -514,6 +514,7 @@ describe Attributor::Hash do
|
|
514
514
|
end
|
515
515
|
end
|
516
516
|
end
|
517
|
+
|
517
518
|
context '#validate' do
|
518
519
|
context 'for a key and value typed hash' do
|
519
520
|
let(:key_type) { Integer }
|
@@ -534,45 +535,85 @@ describe Attributor::Hash do
|
|
534
535
|
context 'for a hash with defined keys' do
|
535
536
|
let(:block) do
|
536
537
|
proc do
|
537
|
-
key 'integer', Integer
|
538
|
+
key 'integer', Integer, null: false # non-nullable, but not required
|
538
539
|
key 'datetime', DateTime
|
539
|
-
key 'not-optional', String, required: true
|
540
|
+
key 'not-optional', String, required: true, null: false # required AND non-nullable
|
541
|
+
key 'required-but-nullable', String, required: true, null: true
|
540
542
|
end
|
541
543
|
end
|
542
544
|
|
543
545
|
let(:type) { Attributor::Hash.construct(block) }
|
544
|
-
|
545
|
-
let(:values) { { 'integer' => 'one', 'datetime' => 'now' } }
|
546
546
|
subject(:hash) { type.new(values) }
|
547
547
|
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
548
|
+
context 'validates it all' do
|
549
|
+
let(:values) { { 'integer' => 'one', 'datetime' => 'now', 'required-but-nullable' => nil} }
|
550
|
+
it 'validates the keys' do
|
551
|
+
errors = hash.validate
|
552
|
+
expect(errors).to have(3).items
|
553
|
+
[
|
554
|
+
'Attribute $.key("integer") received value: "one" is of the wrong type (got: String, expected: Attributor::Integer)',
|
555
|
+
'Attribute $.key("datetime") received value: "now" is of the wrong type (got: String, expected: Attributor::DateTime)',
|
556
|
+
'Attribute $.key("not-optional") is required'
|
557
|
+
].each do |msg|
|
558
|
+
regexp = Regexp.new(Regexp.escape(msg))
|
559
|
+
expect(errors.any?{|err| err =~ regexp}).to be_truthy
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
context 'still validates requiredness even if values are nullable' do
|
565
|
+
let(:values) { {} }
|
566
|
+
it 'complains if not provided' do
|
567
|
+
errors = hash.validate
|
568
|
+
expect(errors).to have(2).items
|
569
|
+
[
|
570
|
+
'Attribute $.key("not-optional") is required',
|
571
|
+
'Attribute $.key("required-but-nullable") is required'
|
572
|
+
].each do |msg|
|
573
|
+
regexp = Regexp.new(Regexp.escape(msg))
|
574
|
+
expect(errors.any?{|err| err =~ regexp}).to be_truthy
|
575
|
+
end
|
576
|
+
end
|
552
577
|
end
|
578
|
+
|
579
|
+
context 'validates nullability regardless of requiredness' do
|
580
|
+
let(:values) { {'integer' => nil, 'not-optional' => nil, 'required-but-nullable' => nil} }
|
581
|
+
it 'complains if null' do
|
582
|
+
errors = hash.validate
|
583
|
+
expect(errors).to have(2).items
|
584
|
+
[
|
585
|
+
'Attribute $.key("integer") is not nullable',
|
586
|
+
'Attribute $.key("not-optional") is not nullable',
|
587
|
+
].each do |msg|
|
588
|
+
regexp = Regexp.new(Regexp.escape(msg))
|
589
|
+
expect(errors.any?{|err| err =~ regexp}).to be_truthy
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
553
594
|
end
|
554
595
|
|
555
596
|
context 'with requirements defined' do
|
556
597
|
let(:type) { Attributor::Hash.construct(block) }
|
557
598
|
|
558
|
-
context 'using requires' do
|
599
|
+
context 'using the requires DSL' do
|
559
600
|
let(:block) do
|
560
601
|
proc do
|
561
602
|
key 'name', String
|
603
|
+
key 'nevernull', String, null: false
|
562
604
|
key 'consistency', Attributor::Boolean
|
563
|
-
key 'availability', Attributor::Boolean
|
564
|
-
key 'partitioning', Attributor::Boolean
|
605
|
+
key 'availability', Attributor::Boolean, null: true
|
606
|
+
key 'partitioning', Attributor::Boolean, null: true
|
565
607
|
requires 'consistency', 'availability'
|
566
|
-
requires.all 'name'
|
608
|
+
requires.all 'name'
|
567
609
|
end
|
568
610
|
end
|
569
611
|
|
570
612
|
it 'complains not all the listed elements are set (false or true)' do
|
571
|
-
errors = type.new('name' => 'CAP').validate
|
613
|
+
errors = type.new('name' => 'CAP', 'consistency' => true, 'nevernull' => nil).validate
|
572
614
|
expect(errors).to have(2).items
|
573
|
-
|
574
|
-
|
575
|
-
end
|
615
|
+
expect(errors).to include('Attribute $.key("nevernull") is not nullable.')
|
616
|
+
expect(errors).to include('Attribute $.key("availability") is required.')
|
576
617
|
end
|
577
618
|
end
|
578
619
|
|
@@ -591,7 +632,7 @@ describe Attributor::Hash do
|
|
591
632
|
errors = type.new('name' => 'CAP', 'consistency' => false).validate
|
592
633
|
expect(errors).to have(1).items
|
593
634
|
expect(errors).to include(
|
594
|
-
'At least 2
|
635
|
+
'At least 2 attributes out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found ["consistency"]'
|
595
636
|
)
|
596
637
|
end
|
597
638
|
end
|
@@ -610,7 +651,7 @@ describe Attributor::Hash do
|
|
610
651
|
it 'complains if more than 2 in the group are set (false or true)' do
|
611
652
|
errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true, 'partitioning' => false).validate
|
612
653
|
expect(errors).to have(1).items
|
613
|
-
expect(errors).to include('At most 2
|
654
|
+
expect(errors).to include('At most 2 attributes out of ["consistency", "availability", "partitioning"] can be passed in for $. Found ["consistency", "availability", "partitioning"]')
|
614
655
|
end
|
615
656
|
end
|
616
657
|
|
@@ -628,12 +669,12 @@ describe Attributor::Hash do
|
|
628
669
|
it 'complains if less than 1 in the group are set (false or true)' do
|
629
670
|
errors = type.new('name' => 'CAP').validate
|
630
671
|
expect(errors).to have(1).items
|
631
|
-
expect(errors).to include('Exactly 1 of the following
|
672
|
+
expect(errors).to include('Exactly 1 of the following attributes ["consistency", "availability", "partitioning"] are required for $. Found 0 instead: []')
|
632
673
|
end
|
633
674
|
it 'complains if more than 1 in the group are set (false or true)' do
|
634
675
|
errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true).validate
|
635
676
|
expect(errors).to have(1).items
|
636
|
-
expect(errors).to include('Exactly 1 of the following
|
677
|
+
expect(errors).to include('Exactly 1 of the following attributes ["consistency", "availability", "partitioning"] are required for $. Found 2 instead: ["consistency", "availability"]')
|
637
678
|
end
|
638
679
|
end
|
639
680
|
|
@@ -651,7 +692,7 @@ describe Attributor::Hash do
|
|
651
692
|
it 'complains if two or more in the group are set (false or true)' do
|
652
693
|
errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true).validate
|
653
694
|
expect(errors).to have(1).items
|
654
|
-
expect(errors).to include('
|
695
|
+
expect(errors).to include('Attributes ["consistency", "availability"] are mutually exclusive for $.')
|
655
696
|
end
|
656
697
|
end
|
657
698
|
|
@@ -676,7 +717,7 @@ describe Attributor::Hash do
|
|
676
717
|
errors = type.new('name' => 'CAP').validate
|
677
718
|
expect(errors).to have(1).items
|
678
719
|
expect(errors).to include(
|
679
|
-
'At least 1
|
720
|
+
'At least 1 attributes out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found none'
|
680
721
|
)
|
681
722
|
end
|
682
723
|
end
|
data/spec/types/model_spec.rb
CHANGED
@@ -371,7 +371,12 @@ describe Attributor::Model do
|
|
371
371
|
context 'for models using the "requires" DSL' do
|
372
372
|
subject(:address) { Address.load({state: 'CA'}) }
|
373
373
|
its(:validate) { should_not be_empty }
|
374
|
-
its(:validate) { should include '
|
374
|
+
its(:validate) { should include 'Attribute $.key(:name) is required.' }
|
375
|
+
end
|
376
|
+
context 'for models with non-nullable attributes' do
|
377
|
+
subject(:address) { Address.load({name: nil, state: nil}) }
|
378
|
+
its(:validate) { should_not be_empty }
|
379
|
+
its(:validate) { should include 'Attribute $.state is not nullable.' } # name is nullable
|
375
380
|
end
|
376
381
|
context 'for models with circular sub-attributes' do
|
377
382
|
context 'that are valid' do
|
@@ -478,6 +483,7 @@ describe Attributor::Model do
|
|
478
483
|
|
479
484
|
it 'supports defining sub-attributes using the proper reference' do
|
480
485
|
expect(struct.attributes[:neighbors].options[:required]).to be true
|
486
|
+
expect(struct.attributes[:neighbors].options[:null]).to be false
|
481
487
|
expect(struct.attributes[:neighbors].type.member_attribute.type.attributes.keys).to match_array [:name, :age]
|
482
488
|
|
483
489
|
name_options = struct.attributes[:neighbors].type.member_attribute.type.attributes[:name].options
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attributor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '6.1'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-02-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: hashie
|
@@ -312,7 +312,6 @@ files:
|
|
312
312
|
- attributor.gemspec
|
313
313
|
- lib/attributor.rb
|
314
314
|
- lib/attributor/attribute.rb
|
315
|
-
- lib/attributor/attribute_resolver.rb
|
316
315
|
- lib/attributor/dsl_compiler.rb
|
317
316
|
- lib/attributor/dumpable.rb
|
318
317
|
- lib/attributor/example_mixin.rb
|
@@ -350,7 +349,6 @@ files:
|
|
350
349
|
- lib/attributor/types/time.rb
|
351
350
|
- lib/attributor/types/uri.rb
|
352
351
|
- lib/attributor/version.rb
|
353
|
-
- spec/attribute_resolver_spec.rb
|
354
352
|
- spec/attribute_spec.rb
|
355
353
|
- spec/attributor_spec.rb
|
356
354
|
- spec/dsl_compiler_spec.rb
|
@@ -407,13 +405,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
407
405
|
- !ruby/object:Gem::Version
|
408
406
|
version: '0'
|
409
407
|
requirements: []
|
410
|
-
|
411
|
-
rubygems_version: 2.6.14
|
408
|
+
rubygems_version: 3.1.2
|
412
409
|
signing_key:
|
413
410
|
specification_version: 4
|
414
411
|
summary: A powerful attribute and type management library for Ruby
|
415
412
|
test_files:
|
416
|
-
- spec/attribute_resolver_spec.rb
|
417
413
|
- spec/attribute_spec.rb
|
418
414
|
- spec/attributor_spec.rb
|
419
415
|
- spec/dsl_compiler_spec.rb
|