attributor 5.4 → 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 +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/attributor/attribute.rb +101 -84
- data/lib/attributor/extras/field_selector.rb +4 -0
- data/lib/attributor/families/numeric.rb +19 -6
- data/lib/attributor/families/temporal.rb +16 -9
- data/lib/attributor/hash_dsl_compiler.rb +6 -5
- data/lib/attributor/type.rb +26 -3
- data/lib/attributor/types/bigdecimal.rb +6 -1
- data/lib/attributor/types/boolean.rb +5 -0
- data/lib/attributor/types/collection.rb +19 -0
- data/lib/attributor/types/csv.rb +4 -0
- data/lib/attributor/types/date.rb +7 -1
- data/lib/attributor/types/date_time.rb +7 -1
- data/lib/attributor/types/float.rb +4 -3
- data/lib/attributor/types/hash.rb +86 -23
- data/lib/attributor/types/integer.rb +7 -1
- data/lib/attributor/types/model.rb +9 -21
- data/lib/attributor/types/object.rb +5 -0
- data/lib/attributor/types/polymorphic.rb +0 -1
- data/lib/attributor/types/string.rb +19 -0
- data/lib/attributor/types/symbol.rb +5 -0
- data/lib/attributor/types/tempfile.rb +4 -0
- data/lib/attributor/types/time.rb +6 -2
- data/lib/attributor/types/uri.rb +8 -0
- data/lib/attributor/version.rb +1 -1
- data/lib/attributor.rb +3 -7
- data/spec/attribute_spec.rb +148 -124
- data/spec/extras/field_selector/field_selector_spec.rb +9 -0
- data/spec/hash_dsl_compiler_spec.rb +5 -5
- data/spec/spec_helper.rb +0 -2
- data/spec/support/integers.rb +7 -0
- data/spec/support/models.rb +7 -7
- data/spec/types/bigdecimal_spec.rb +8 -0
- data/spec/types/boolean_spec.rb +10 -0
- data/spec/types/collection_spec.rb +16 -0
- data/spec/types/date_spec.rb +9 -0
- data/spec/types/date_time_spec.rb +9 -0
- data/spec/types/float_spec.rb +8 -0
- data/spec/types/hash_spec.rb +181 -22
- data/spec/types/integer_spec.rb +9 -0
- data/spec/types/model_spec.rb +7 -1
- data/spec/types/string_spec.rb +10 -0
- data/spec/types/temporal_spec.rb +5 -1
- data/spec/types/time_spec.rb +9 -0
- data/spec/types/uri_spec.rb +9 -0
- metadata +5 -6
- data/lib/attributor/attribute_resolver.rb +0 -111
- data/spec/attribute_resolver_spec.rb +0 -237
data/spec/types/hash_spec.rb
CHANGED
@@ -7,6 +7,7 @@ describe Attributor::Hash do
|
|
7
7
|
its(:key_type) { should be(Attributor::Object) }
|
8
8
|
its(:value_type) { should be(Attributor::Object) }
|
9
9
|
its(:dsl_class) { should be(Attributor::HashDSLCompiler) }
|
10
|
+
its(:json_schema_type) { should be(:object) }
|
10
11
|
|
11
12
|
context 'attributes' do
|
12
13
|
context 'with an exception from the definition block' do
|
@@ -513,6 +514,7 @@ describe Attributor::Hash do
|
|
513
514
|
end
|
514
515
|
end
|
515
516
|
end
|
517
|
+
|
516
518
|
context '#validate' do
|
517
519
|
context 'for a key and value typed hash' do
|
518
520
|
let(:key_type) { Integer }
|
@@ -533,45 +535,85 @@ describe Attributor::Hash do
|
|
533
535
|
context 'for a hash with defined keys' do
|
534
536
|
let(:block) do
|
535
537
|
proc do
|
536
|
-
key 'integer', Integer
|
538
|
+
key 'integer', Integer, null: false # non-nullable, but not required
|
537
539
|
key 'datetime', DateTime
|
538
|
-
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
|
539
542
|
end
|
540
543
|
end
|
541
544
|
|
542
545
|
let(:type) { Attributor::Hash.construct(block) }
|
543
|
-
|
544
|
-
let(:values) { { 'integer' => 'one', 'datetime' => 'now' } }
|
545
546
|
subject(:hash) { type.new(values) }
|
546
547
|
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
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
|
551
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
|
+
|
552
594
|
end
|
553
595
|
|
554
596
|
context 'with requirements defined' do
|
555
597
|
let(:type) { Attributor::Hash.construct(block) }
|
556
598
|
|
557
|
-
context 'using requires' do
|
599
|
+
context 'using the requires DSL' do
|
558
600
|
let(:block) do
|
559
601
|
proc do
|
560
602
|
key 'name', String
|
603
|
+
key 'nevernull', String, null: false
|
561
604
|
key 'consistency', Attributor::Boolean
|
562
|
-
key 'availability', Attributor::Boolean
|
563
|
-
key 'partitioning', Attributor::Boolean
|
605
|
+
key 'availability', Attributor::Boolean, null: true
|
606
|
+
key 'partitioning', Attributor::Boolean, null: true
|
564
607
|
requires 'consistency', 'availability'
|
565
|
-
requires.all 'name'
|
608
|
+
requires.all 'name'
|
566
609
|
end
|
567
610
|
end
|
568
611
|
|
569
612
|
it 'complains not all the listed elements are set (false or true)' do
|
570
|
-
errors = type.new('name' => 'CAP').validate
|
613
|
+
errors = type.new('name' => 'CAP', 'consistency' => true, 'nevernull' => nil).validate
|
571
614
|
expect(errors).to have(2).items
|
572
|
-
|
573
|
-
|
574
|
-
end
|
615
|
+
expect(errors).to include('Attribute $.key("nevernull") is not nullable.')
|
616
|
+
expect(errors).to include('Attribute $.key("availability") is required.')
|
575
617
|
end
|
576
618
|
end
|
577
619
|
|
@@ -590,7 +632,7 @@ describe Attributor::Hash do
|
|
590
632
|
errors = type.new('name' => 'CAP', 'consistency' => false).validate
|
591
633
|
expect(errors).to have(1).items
|
592
634
|
expect(errors).to include(
|
593
|
-
'At least 2
|
635
|
+
'At least 2 attributes out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found ["consistency"]'
|
594
636
|
)
|
595
637
|
end
|
596
638
|
end
|
@@ -609,7 +651,7 @@ describe Attributor::Hash do
|
|
609
651
|
it 'complains if more than 2 in the group are set (false or true)' do
|
610
652
|
errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true, 'partitioning' => false).validate
|
611
653
|
expect(errors).to have(1).items
|
612
|
-
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"]')
|
613
655
|
end
|
614
656
|
end
|
615
657
|
|
@@ -627,12 +669,12 @@ describe Attributor::Hash do
|
|
627
669
|
it 'complains if less than 1 in the group are set (false or true)' do
|
628
670
|
errors = type.new('name' => 'CAP').validate
|
629
671
|
expect(errors).to have(1).items
|
630
|
-
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: []')
|
631
673
|
end
|
632
674
|
it 'complains if more than 1 in the group are set (false or true)' do
|
633
675
|
errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true).validate
|
634
676
|
expect(errors).to have(1).items
|
635
|
-
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"]')
|
636
678
|
end
|
637
679
|
end
|
638
680
|
|
@@ -650,7 +692,7 @@ describe Attributor::Hash do
|
|
650
692
|
it 'complains if two or more in the group are set (false or true)' do
|
651
693
|
errors = type.new('name' => 'CAP', 'consistency' => false, 'availability' => true).validate
|
652
694
|
expect(errors).to have(1).items
|
653
|
-
expect(errors).to include('
|
695
|
+
expect(errors).to include('Attributes ["consistency", "availability"] are mutually exclusive for $.')
|
654
696
|
end
|
655
697
|
end
|
656
698
|
|
@@ -675,7 +717,7 @@ describe Attributor::Hash do
|
|
675
717
|
errors = type.new('name' => 'CAP').validate
|
676
718
|
expect(errors).to have(1).items
|
677
719
|
expect(errors).to include(
|
678
|
-
'At least 1
|
720
|
+
'At least 1 attributes out of ["consistency", "availability", "partitioning"] are required to be passed in for $. Found none'
|
679
721
|
)
|
680
722
|
end
|
681
723
|
end
|
@@ -1172,4 +1214,121 @@ describe Attributor::Hash do
|
|
1172
1214
|
|
1173
1215
|
context Attributor::InvalidDefinition do
|
1174
1216
|
end
|
1217
|
+
|
1218
|
+
|
1219
|
+
context '.as_json_hash' do
|
1220
|
+
let(:example){ nil }
|
1221
|
+
subject(:description) { type.as_json_schema(example: example) }
|
1222
|
+
its([:type]){ should eq(:object)}
|
1223
|
+
its([:'x-type_name']){ should eq('Hash')}
|
1224
|
+
|
1225
|
+
context 'for hashes with explicit key and value types' do
|
1226
|
+
let(:key_type){ String }
|
1227
|
+
let(:value_type){ Integer }
|
1228
|
+
|
1229
|
+
subject(:type) { Attributor::Hash.of(key: key_type, value: value_type) }
|
1230
|
+
|
1231
|
+
it 'describes the key type correctly' do
|
1232
|
+
expect(description.keys).to include( :'x-key_type' )
|
1233
|
+
expect(description[:'x-key_type']).to be_kind_of(::Hash)
|
1234
|
+
expect(description[:'x-key_type'][:type]).to eq( :string )
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
it 'describes the value type correctly' do
|
1238
|
+
expect(description.keys).to include( :'x-value_type' )
|
1239
|
+
expect(description[:'x-value_type']).to be_kind_of(::Hash)
|
1240
|
+
expect(description[:'x-value_type'][:type]).to eq( :integer )
|
1241
|
+
end
|
1242
|
+
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
|
1246
|
+
context 'for hashes with specific keys defined' do
|
1247
|
+
let(:block) do
|
1248
|
+
proc do
|
1249
|
+
key 'a string', String
|
1250
|
+
key '1', Integer, min: 1, max: 20
|
1251
|
+
key 'some_date', DateTime
|
1252
|
+
key 'defaulted', String, default: 'default value'
|
1253
|
+
requires do
|
1254
|
+
all.of '1','some_date'
|
1255
|
+
exclusive 'some_date', 'defaulted'
|
1256
|
+
at_least(1).of 'a string', 'some_date'
|
1257
|
+
at_most(2).of 'a string', 'some_date'
|
1258
|
+
exactly(1).of 'a string', 'some_date'
|
1259
|
+
end
|
1260
|
+
end
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
let(:type) { Attributor::Hash.of(key: String).construct(block) }
|
1264
|
+
|
1265
|
+
it 'describes the basic type options correctly' do
|
1266
|
+
expect(description[:type]).to eq(:object)
|
1267
|
+
expect(description[:'x-key_type']).to eq( type: :string , 'x-type_name': 'String')
|
1268
|
+
expect(description).to_not have_key(:'x-value_type')
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
it 'describes the type attributes correctly' do
|
1272
|
+
props = description[:properties]
|
1273
|
+
|
1274
|
+
expect(props['a string']).to eq(type: :string, 'x-type_name': 'String')
|
1275
|
+
expect(props['1']).to eq(type: :integer, 'x-type_name': 'Integer', minimum: 1, maximum: 20)
|
1276
|
+
expect(props['some_date']).to eq(type: :string, 'x-type_name': 'DateTime', format: :'date-time')
|
1277
|
+
expect(props['defaulted']).to eq(type: :string, 'x-type_name': 'String', default: 'default value')
|
1278
|
+
end
|
1279
|
+
|
1280
|
+
it 'describes the attribute requirements correctly' do
|
1281
|
+
reqs = description[:required]
|
1282
|
+
expect(reqs).to be_kind_of(Array)
|
1283
|
+
expect(reqs).to eq( ['1','some_date'] )
|
1284
|
+
end
|
1285
|
+
|
1286
|
+
it 'describes the extended requirements correctly' do
|
1287
|
+
reqs = description[:'x-requirements']
|
1288
|
+
expect(reqs).to be_kind_of(Array)
|
1289
|
+
expect(reqs.size).to be(5)
|
1290
|
+
expect(reqs).to include( type: :all, attributes: ['1','some_date'] )
|
1291
|
+
expect(reqs).to include( type: :exclusive, attributes: ['some_date','defaulted'] )
|
1292
|
+
expect(reqs).to include( type: :at_least, attributes: ['a string','some_date'], count: 1 )
|
1293
|
+
expect(reqs).to include( type: :at_most, attributes: ['a string','some_date'], count: 2 )
|
1294
|
+
expect(reqs).to include( type: :exactly, attributes: ['a string','some_date'], count: 1 )
|
1295
|
+
end
|
1296
|
+
|
1297
|
+
context 'merging requires.all with attribute required: true' do
|
1298
|
+
let(:block) do
|
1299
|
+
proc do
|
1300
|
+
key 'required string', String, required: true
|
1301
|
+
key '1', Integer
|
1302
|
+
key 'some_date', DateTime
|
1303
|
+
requires do
|
1304
|
+
all.of 'some_date'
|
1305
|
+
end
|
1306
|
+
end
|
1307
|
+
end
|
1308
|
+
it 'includes attributes with required: true into :required' do
|
1309
|
+
expect(description[:required].size).to eq(2)
|
1310
|
+
expect(description[:required]).to include( 'required string','some_date' )
|
1311
|
+
end
|
1312
|
+
|
1313
|
+
it 'includes attributes with required: true into the :all requirements' do
|
1314
|
+
req_all = description[:'x-requirements'].select{|r| r[:type] == :all}.first
|
1315
|
+
expect(req_all[:attributes]).to include( 'required string','some_date' )
|
1316
|
+
end
|
1317
|
+
end
|
1318
|
+
|
1319
|
+
|
1320
|
+
context 'with an example' do
|
1321
|
+
let(:example){ type.example }
|
1322
|
+
|
1323
|
+
it 'should have the matching example for each leaf key' do
|
1324
|
+
expect(description[:properties].keys).to include(*type.keys.keys)
|
1325
|
+
description[:properties].each do |name,sub_description|
|
1326
|
+
expect(sub_description).to have_key(:example)
|
1327
|
+
val = type.attributes[name].dump(example[name])
|
1328
|
+
expect(sub_description[:example]).to eq val
|
1329
|
+
end
|
1330
|
+
end
|
1331
|
+
end
|
1332
|
+
end
|
1333
|
+
end
|
1175
1334
|
end
|
data/spec/types/integer_spec.rb
CHANGED
@@ -146,4 +146,13 @@ describe Attributor::Integer do
|
|
146
146
|
end
|
147
147
|
end
|
148
148
|
end
|
149
|
+
|
150
|
+
context '.as_json_schema' do
|
151
|
+
subject(:js){ type.as_json_schema }
|
152
|
+
it 'adds the right stuff' do
|
153
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
154
|
+
expect(js[:type]).to eq(:integer)
|
155
|
+
expect(js[:'x-type_name']).to eq('Integer')
|
156
|
+
end
|
157
|
+
end
|
149
158
|
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
|
data/spec/types/string_spec.rb
CHANGED
@@ -64,4 +64,14 @@ describe Attributor::String do
|
|
64
64
|
end.to raise_error(Attributor::IncompatibleTypeError)
|
65
65
|
end
|
66
66
|
end
|
67
|
+
|
68
|
+
context '.as_json_schema' do
|
69
|
+
subject(:js){ type.as_json_schema(attribute_options: { regexp: /^Foobar$/ }) }
|
70
|
+
it 'adds the right attributes' do
|
71
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
72
|
+
expect(js[:type]).to eq(:string)
|
73
|
+
expect(js[:'x-type_name']).to eq('String')
|
74
|
+
expect(js[:pattern]).to eq('^Foobar$')
|
75
|
+
end
|
76
|
+
end
|
67
77
|
end
|
data/spec/types/temporal_spec.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Attributor::Temporal do
|
4
|
-
subject(:type)
|
4
|
+
subject(:type) do
|
5
|
+
Class.new do
|
6
|
+
include Attributor::Temporal
|
7
|
+
end
|
8
|
+
end
|
5
9
|
|
6
10
|
it 'raises an exception for native_type' do
|
7
11
|
expect { type.native_type }.to raise_error(NotImplementedError)
|
data/spec/types/time_spec.rb
CHANGED
@@ -90,4 +90,13 @@ describe Attributor::Time do
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
93
|
+
context '.as_json_schema' do
|
94
|
+
subject(:js){ type.as_json_schema }
|
95
|
+
it 'adds the right attributes' do
|
96
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
97
|
+
expect(js[:type]).to eq(:string)
|
98
|
+
expect(js[:format]).to eq(:'time')
|
99
|
+
expect(js[:'x-type_name']).to eq('Time')
|
100
|
+
end
|
101
|
+
end
|
93
102
|
end
|
data/spec/types/uri_spec.rb
CHANGED
@@ -109,4 +109,13 @@ describe Attributor::URI do
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
end
|
112
|
+
context '.as_json_schema' do
|
113
|
+
subject(:js){ type.as_json_schema }
|
114
|
+
it 'adds the right attributes' do
|
115
|
+
expect(js.keys).to include(:type, :'x-type_name')
|
116
|
+
expect(js[:type]).to eq(:string)
|
117
|
+
expect(js[:format]).to eq(:uri)
|
118
|
+
expect(js[:'x-type_name']).to eq('URI')
|
119
|
+
end
|
120
|
+
end
|
112
121
|
end
|
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.0'
|
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: 2021-11-22 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
|
@@ -361,6 +359,7 @@ files:
|
|
361
359
|
- spec/smart_attribute_selector_spec.rb
|
362
360
|
- spec/spec_helper.rb
|
363
361
|
- spec/support/hashes.rb
|
362
|
+
- spec/support/integers.rb
|
364
363
|
- spec/support/models.rb
|
365
364
|
- spec/support/polymorphics.rb
|
366
365
|
- spec/type_spec.rb
|
@@ -406,12 +405,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
406
405
|
- !ruby/object:Gem::Version
|
407
406
|
version: '0'
|
408
407
|
requirements: []
|
409
|
-
rubygems_version: 3.
|
408
|
+
rubygems_version: 3.1.2
|
410
409
|
signing_key:
|
411
410
|
specification_version: 4
|
412
411
|
summary: A powerful attribute and type management library for Ruby
|
413
412
|
test_files:
|
414
|
-
- spec/attribute_resolver_spec.rb
|
415
413
|
- spec/attribute_spec.rb
|
416
414
|
- spec/attributor_spec.rb
|
417
415
|
- spec/dsl_compiler_spec.rb
|
@@ -422,6 +420,7 @@ test_files:
|
|
422
420
|
- spec/smart_attribute_selector_spec.rb
|
423
421
|
- spec/spec_helper.rb
|
424
422
|
- spec/support/hashes.rb
|
423
|
+
- spec/support/integers.rb
|
425
424
|
- spec/support/models.rb
|
426
425
|
- spec/support/polymorphics.rb
|
427
426
|
- spec/type_spec.rb
|
@@ -1,111 +0,0 @@
|
|
1
|
-
require 'ostruct'
|
2
|
-
|
3
|
-
module Attributor
|
4
|
-
class AttributeResolver
|
5
|
-
ROOT_PREFIX = '$'.freeze
|
6
|
-
COLLECTION_INDEX_KEY = /^at\((\d+)\)$/
|
7
|
-
|
8
|
-
class Data < ::Hash
|
9
|
-
include Hashie::Extensions::MethodReader
|
10
|
-
end
|
11
|
-
|
12
|
-
attr_reader :data
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
@data = Data.new
|
16
|
-
end
|
17
|
-
|
18
|
-
def query!(key_path, path_prefix = ROOT_PREFIX)
|
19
|
-
# If the incoming key_path is not an absolute path, append the given prefix
|
20
|
-
# NOTE: Need to index key_path by range here because Ruby 1.8 returns a
|
21
|
-
# FixNum for the ASCII code, not the actual character, when indexing by a number.
|
22
|
-
unless key_path[0..0] == ROOT_PREFIX
|
23
|
-
# TODO: prepend path_prefix to path_prefix if it did not include it? hm.
|
24
|
-
key_path = path_prefix + SEPARATOR + key_path
|
25
|
-
end
|
26
|
-
|
27
|
-
# Discard the initial element, which should always be ROOT_PREFIX at this point
|
28
|
-
_root, *path = key_path.split(SEPARATOR)
|
29
|
-
|
30
|
-
# Follow the hierarchy path to the requested node and return it:
|
31
|
-
# Example path => ["instance", "ssh_key", "name"]
|
32
|
-
# Example @data => {"instance" => { "ssh_key" => { "name" => "foobar" } }}
|
33
|
-
#
|
34
|
-
# at(n) is a collection index:
|
35
|
-
# Example path => ["filters", "at(0)", "type"]
|
36
|
-
# Example data => {"filters" => [{ "type" => "instance:tag" }]}
|
37
|
-
#
|
38
|
-
result = path.inject(@data) do |hash, key|
|
39
|
-
return nil if hash.nil?
|
40
|
-
if (match = key.match(COLLECTION_INDEX_KEY))
|
41
|
-
hash[match[1].to_i]
|
42
|
-
else
|
43
|
-
hash.send key
|
44
|
-
end
|
45
|
-
end
|
46
|
-
result
|
47
|
-
end
|
48
|
-
|
49
|
-
# Query for a certain key in the attribute hierarchy
|
50
|
-
#
|
51
|
-
# @param [String] key_path The name of the key to query and its path
|
52
|
-
# @param [String] path_prefix
|
53
|
-
#
|
54
|
-
# @return [String] The value of the specified attribute/key
|
55
|
-
#
|
56
|
-
def query(key_path, path_prefix = ROOT_PREFIX)
|
57
|
-
query!(key_path, path_prefix)
|
58
|
-
rescue NoMethodError
|
59
|
-
nil
|
60
|
-
end
|
61
|
-
|
62
|
-
def register(key_path, value)
|
63
|
-
if key_path.split(SEPARATOR).size > 1
|
64
|
-
raise AttributorException, "can only register top-level attributes. got: #{key_path}"
|
65
|
-
end
|
66
|
-
|
67
|
-
@data[key_path] = value
|
68
|
-
end
|
69
|
-
|
70
|
-
# Checks that the the condition is met. This means the attribute identified
|
71
|
-
# by path_prefix and key_path satisfies the optional predicate, which when
|
72
|
-
# nil simply checks for existence.
|
73
|
-
#
|
74
|
-
# @param path_prefix [String]
|
75
|
-
# @param key_path [String]
|
76
|
-
# @param predicate [String|Regexp|Proc|NilClass]
|
77
|
-
#
|
78
|
-
# @returns [Boolean] True if :required_if condition is met, false otherwise
|
79
|
-
#
|
80
|
-
# @raise [AttributorException] When an unsupported predicate is passed
|
81
|
-
#
|
82
|
-
def check(path_prefix, key_path, predicate = nil)
|
83
|
-
value = query(key_path, path_prefix)
|
84
|
-
|
85
|
-
# we have a value, any value, which is good enough given no predicate
|
86
|
-
return true if !value.nil? && predicate.nil?
|
87
|
-
|
88
|
-
case predicate
|
89
|
-
when ::String, ::Regexp, ::Integer, ::Float, ::DateTime, true, false
|
90
|
-
return predicate === value
|
91
|
-
when ::Proc
|
92
|
-
# Cannot use === here as above due to different behavior in Ruby 1.8
|
93
|
-
return predicate.call(value)
|
94
|
-
when nil
|
95
|
-
return !value.nil?
|
96
|
-
else
|
97
|
-
raise AttributorException, "predicate not supported: #{predicate.inspect}"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# TODO: kill this when we also kill Taylor's IdentityMap.current
|
102
|
-
def self.current=(resolver)
|
103
|
-
Thread.current[:_attributor_attribute_resolver] = resolver
|
104
|
-
end
|
105
|
-
|
106
|
-
def self.current
|
107
|
-
raise AttributorException, 'No AttributeResolver set.' unless Thread.current[:_attributor_attribute_resolver]
|
108
|
-
Thread.current[:_attributor_attribute_resolver]
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|