hash_cast 0.5.0 → 0.5.2
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 +6 -0
- data/Gemfile.lock +1 -1
- data/lib/hash_cast/caster.rb +13 -8
- data/lib/hash_cast/casters/string_caster.rb +13 -0
- data/lib/hash_cast/config.rb +7 -1
- data/lib/hash_cast/{attributes_caster.rb → recursive_caster_applicator.rb} +3 -3
- data/lib/hash_cast/version.rb +1 -1
- data/lib/hash_cast.rb +11 -1
- data/spec/hash_cast/caster_spec.rb +125 -69
- data/spec/hash_cast/hash_cast_spec.rb +4 -4
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 899228916d29fc1b30a38151b850f6734ea57cb7390561b6a3aaecd0e3e1a7e6
|
4
|
+
data.tar.gz: b055bcb17b4863fea30a1f5b3a6e739514aaff9ca853fcefb3ee702bed3ece5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ebd62549257464c42f8d9550b5a3a592e9b84fc430619821818497d74bc1c95946effc1c21188a619834f6420b3c78fef2aac6f9859289da0ec7719188e9726
|
7
|
+
data.tar.gz: 0134f1c7695a0c205e0b9f57fa6ed995fc568e55e1e999b37d80973ac102c0450d2bde94eaf20c7c72f74f534d9da6d9432a017224d1bac4eb4b05e8b04e9f2c
|
data/CHANGELOG.md
ADDED
data/Gemfile.lock
CHANGED
data/lib/hash_cast/caster.rb
CHANGED
@@ -99,21 +99,26 @@ module HashCast::Caster
|
|
99
99
|
# @param hash [Hash] hash for casting
|
100
100
|
# @param options [Hash] options, input_keys: :string, output_key: :symbol
|
101
101
|
def cast(hash, options = {})
|
102
|
-
|
103
|
-
check_hash_given!(hash)
|
104
|
-
check_options!(options)
|
105
|
-
set_default_options(options)
|
106
|
-
|
107
|
-
attributes_caster = HashCast::AttributesCaster.new(class_variable_get(:@@attributes), options)
|
108
|
-
attributes_caster.cast(hash)
|
102
|
+
cast_with_attributes(hash, get_attributes, options)
|
109
103
|
end
|
110
104
|
|
111
105
|
private
|
112
106
|
|
113
|
-
def
|
107
|
+
def get_attributes
|
114
108
|
unless class_variable_defined?(:@@attributes)
|
115
109
|
raise HashCast::Errors::ArgumentError, "Attributes block should be defined"
|
116
110
|
end
|
111
|
+
|
112
|
+
class_variable_get(:@@attributes)
|
113
|
+
end
|
114
|
+
|
115
|
+
def cast_with_attributes(hash, attributes, options = {})
|
116
|
+
check_hash_given!(hash)
|
117
|
+
check_options!(options)
|
118
|
+
set_default_options(options)
|
119
|
+
|
120
|
+
caster_applicator = HashCast::RecursiveCasterApplicator.new(attributes, options)
|
121
|
+
caster_applicator.cast(hash)
|
117
122
|
end
|
118
123
|
|
119
124
|
def check_options!(options)
|
@@ -1,6 +1,19 @@
|
|
1
1
|
class HashCast::Casters::StringCaster
|
2
|
+
NULL_BYTE_CHARACTER = "\u0000".freeze
|
2
3
|
|
3
4
|
def self.cast(value, attr_name, options = {})
|
5
|
+
casted_value = cast_string(value, attr_name, options)
|
6
|
+
|
7
|
+
if HashCast.config.validate_string_null_byte && casted_value.match?(NULL_BYTE_CHARACTER)
|
8
|
+
raise HashCast::Errors::CastingError, 'contains invalid characters'
|
9
|
+
end
|
10
|
+
|
11
|
+
casted_value
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def self.cast_string(value, attr_name, options = {})
|
4
17
|
if value.is_a?(String)
|
5
18
|
value
|
6
19
|
elsif value.is_a?(Symbol)
|
data/lib/hash_cast/config.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class HashCast::Config
|
2
|
-
attr_accessor :input_keys, :output_keys
|
2
|
+
attr_accessor :input_keys, :output_keys, :validate_string_null_byte
|
3
3
|
|
4
4
|
def input_keys
|
5
5
|
@input_keys || :symbol
|
@@ -8,4 +8,10 @@ class HashCast::Config
|
|
8
8
|
def output_keys
|
9
9
|
@output_keys || :symbol
|
10
10
|
end
|
11
|
+
|
12
|
+
def validate_string_null_byte
|
13
|
+
return true if @validate_string_null_byte.nil?
|
14
|
+
|
15
|
+
@validate_string_null_byte
|
16
|
+
end
|
11
17
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class HashCast::
|
1
|
+
class HashCast::RecursiveCasterApplicator
|
2
2
|
attr_reader :attributes, :options
|
3
3
|
|
4
4
|
def initialize(attributes, options)
|
@@ -19,11 +19,11 @@ class HashCast::AttributesCaster
|
|
19
19
|
handle_attribute_error(e, attribute)
|
20
20
|
end
|
21
21
|
else
|
22
|
-
raise HashCast::Errors::MissingAttributeError.new("should be given", attribute.name)if attribute.required?
|
22
|
+
raise HashCast::Errors::MissingAttributeError.new("should be given", attribute.name) if attribute.required?
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
if !options[:skip_unexpected_attributes]
|
26
|
+
if options.has_key?(:skip_unexpected_attributes) && !options[:skip_unexpected_attributes]
|
27
27
|
check_unexpected_attributes_not_given!(hash_keys, casted_hash.keys)
|
28
28
|
end
|
29
29
|
|
data/lib/hash_cast/version.rb
CHANGED
data/lib/hash_cast.rb
CHANGED
@@ -5,7 +5,7 @@ require 'hash_cast/casters'
|
|
5
5
|
require 'hash_cast/concern.rb'
|
6
6
|
require 'hash_cast/metadata/attribute'
|
7
7
|
require 'hash_cast/attributes_parser'
|
8
|
-
require 'hash_cast/
|
8
|
+
require 'hash_cast/recursive_caster_applicator'
|
9
9
|
require 'hash_cast/caster'
|
10
10
|
|
11
11
|
module HashCast
|
@@ -32,6 +32,16 @@ module HashCast
|
|
32
32
|
def self.config
|
33
33
|
@@config ||= HashCast::Config.new
|
34
34
|
end
|
35
|
+
|
36
|
+
def self.create(&block)
|
37
|
+
Class.new do
|
38
|
+
include HashCast::Caster
|
39
|
+
|
40
|
+
attributes do
|
41
|
+
instance_exec(&block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
35
45
|
end
|
36
46
|
|
37
47
|
HashCast.add_caster(:array, HashCast::Casters::ArrayCaster)
|
@@ -54,7 +54,7 @@ describe HashCast::Caster do
|
|
54
54
|
|
55
55
|
casted_hash = ContactCaster.cast(input_hash)
|
56
56
|
|
57
|
-
casted_hash.
|
57
|
+
expect(casted_hash).to eq({
|
58
58
|
contact: {
|
59
59
|
name: "John Smith",
|
60
60
|
age: 22,
|
@@ -77,7 +77,7 @@ describe HashCast::Caster do
|
|
77
77
|
},
|
78
78
|
]
|
79
79
|
}
|
80
|
-
}
|
80
|
+
})
|
81
81
|
end
|
82
82
|
|
83
83
|
describe "Custom casters" do
|
@@ -119,14 +119,14 @@ describe HashCast::Caster do
|
|
119
119
|
]
|
120
120
|
)
|
121
121
|
|
122
|
-
casted_hash.
|
122
|
+
expect(casted_hash).to eq({
|
123
123
|
name: "Might & Magic",
|
124
124
|
settings: { account: "migthy_lord" },
|
125
125
|
emails: [
|
126
126
|
{ address: "test1@example.com" },
|
127
127
|
{ address: "test2@example.com" }
|
128
128
|
]
|
129
|
-
}
|
129
|
+
})
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
@@ -221,69 +221,6 @@ describe HashCast::Caster do
|
|
221
221
|
end.to_not raise_error
|
222
222
|
end
|
223
223
|
|
224
|
-
it "should raise error if unexpected attribute was given" do
|
225
|
-
input_hash = {
|
226
|
-
contact: {
|
227
|
-
wrong_attribute: 'foo',
|
228
|
-
name: "Jim",
|
229
|
-
weight: 65.5,
|
230
|
-
birthday: Date.today,
|
231
|
-
last_logged_in: DateTime.now,
|
232
|
-
last_visited_at: Time.now,
|
233
|
-
company: {
|
234
|
-
name: "MyCo",
|
235
|
-
},
|
236
|
-
emails: [ "test@example.com", "test2@example.com" ],
|
237
|
-
social_accounts: [
|
238
|
-
{
|
239
|
-
name: "john_smith",
|
240
|
-
type: :twitter,
|
241
|
-
},
|
242
|
-
{
|
243
|
-
name: "John",
|
244
|
-
type: :facebook,
|
245
|
-
},
|
246
|
-
]
|
247
|
-
}
|
248
|
-
}
|
249
|
-
|
250
|
-
expect do
|
251
|
-
ContactCaster.cast(input_hash)
|
252
|
-
end.to raise_error(HashCast::Errors::UnexpectedAttributeError, "contact[wrong_attribute] is not valid attribute name")
|
253
|
-
end
|
254
|
-
|
255
|
-
it "shouldn't unexpected attributes error if skip_unexpected_attributes flag is set to true" do
|
256
|
-
input_hash = {
|
257
|
-
contact: {
|
258
|
-
wrong_attribute: 'foo',
|
259
|
-
name: "Jim",
|
260
|
-
weight: 65.5,
|
261
|
-
birthday: Date.today,
|
262
|
-
last_logged_in: DateTime.now,
|
263
|
-
last_visited_at: Time.now,
|
264
|
-
company: {
|
265
|
-
name: "MyCo",
|
266
|
-
},
|
267
|
-
emails: [ "test@example.com", "test2@example.com" ],
|
268
|
-
social_accounts: [
|
269
|
-
{
|
270
|
-
name: "john_smith",
|
271
|
-
type: :twitter,
|
272
|
-
},
|
273
|
-
{
|
274
|
-
name: "John",
|
275
|
-
type: :facebook,
|
276
|
-
},
|
277
|
-
]
|
278
|
-
}
|
279
|
-
}
|
280
|
-
|
281
|
-
expect do
|
282
|
-
ContactCaster.cast(input_hash, skip_unexpected_attributes: true)
|
283
|
-
end.not_to raise_error(HashCast::Errors::UnexpectedAttributeError)
|
284
|
-
|
285
|
-
end
|
286
|
-
|
287
224
|
it "should convert accept hash with string keys and cast them to symbol keys" do
|
288
225
|
input_hash = {
|
289
226
|
'contact' => {
|
@@ -312,7 +249,7 @@ describe HashCast::Caster do
|
|
312
249
|
|
313
250
|
casted_hash = ContactCaster.cast(input_hash, input_keys: :string, output_keys: :symbol)
|
314
251
|
|
315
|
-
casted_hash.
|
252
|
+
expect(casted_hash).to eq({
|
316
253
|
contact: {
|
317
254
|
name: "John Smith",
|
318
255
|
age: 22,
|
@@ -335,12 +272,106 @@ describe HashCast::Caster do
|
|
335
272
|
},
|
336
273
|
]
|
337
274
|
}
|
275
|
+
})
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
context "checking unexpected attributes" do
|
280
|
+
before(:all) do
|
281
|
+
class BillingDetailsCaster
|
282
|
+
include HashCast::Caster
|
283
|
+
|
284
|
+
attributes do
|
285
|
+
string :name, optional: true
|
286
|
+
|
287
|
+
array :contacts, each: :hash, optional: true do
|
288
|
+
string :email
|
289
|
+
end
|
290
|
+
|
291
|
+
hash :address, optional: true do
|
292
|
+
string :city
|
293
|
+
string :country
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
it "doesn't raise unexpected attributes error by default" do
|
300
|
+
input_hash = {
|
301
|
+
wrong_attribute: 'foo',
|
302
|
+
name: "Jim",
|
303
|
+
address: {
|
304
|
+
city: "New York",
|
305
|
+
country: "USA",
|
306
|
+
street: "Random street"
|
307
|
+
},
|
308
|
+
contacts: [
|
309
|
+
{
|
310
|
+
name: "john_smith",
|
311
|
+
email: "john@example.com",
|
312
|
+
}
|
313
|
+
]
|
314
|
+
}
|
315
|
+
|
316
|
+
output_hash = BillingDetailsCaster.cast(input_hash)
|
317
|
+
|
318
|
+
expect(output_hash).to eq({
|
319
|
+
name: "Jim",
|
320
|
+
address: {
|
321
|
+
city: "New York",
|
322
|
+
country: "USA"
|
323
|
+
},
|
324
|
+
contacts: [
|
325
|
+
{
|
326
|
+
email: "john@example.com",
|
327
|
+
}
|
328
|
+
]
|
329
|
+
})
|
330
|
+
end
|
331
|
+
|
332
|
+
it "raise error for unexpected root attribute if skip_unexpected_attributes=false" do
|
333
|
+
input_hash = {
|
334
|
+
wrong_attribute: 'foo',
|
335
|
+
name: "Jim"
|
338
336
|
}
|
337
|
+
|
338
|
+
expect do
|
339
|
+
BillingDetailsCaster.cast(input_hash, skip_unexpected_attributes: false)
|
340
|
+
end.to raise_error(HashCast::Errors::UnexpectedAttributeError, "wrong_attribute is not valid attribute name")
|
341
|
+
end
|
342
|
+
|
343
|
+
it "raise error for unexpected root attribute if skip_unexpected_attributes=false" do
|
344
|
+
input_hash = {
|
345
|
+
name: "Jim",
|
346
|
+
address: {
|
347
|
+
city: "New York",
|
348
|
+
country: "USA",
|
349
|
+
street: "Random street"
|
350
|
+
}
|
351
|
+
}
|
352
|
+
|
353
|
+
expect do
|
354
|
+
BillingDetailsCaster.cast(input_hash, skip_unexpected_attributes: false)
|
355
|
+
end.to raise_error(HashCast::Errors::UnexpectedAttributeError, "address[street] is not valid attribute name")
|
356
|
+
end
|
357
|
+
|
358
|
+
it "raise error for unexpected root attribute if skip_unexpected_attributes=false" do
|
359
|
+
input_hash = {
|
360
|
+
name: "Jim",
|
361
|
+
contacts: [{
|
362
|
+
name: "john_smith",
|
363
|
+
email: "john@example.com",
|
364
|
+
}]
|
365
|
+
}
|
366
|
+
|
367
|
+
expect do
|
368
|
+
BillingDetailsCaster.cast(input_hash, skip_unexpected_attributes: false)
|
369
|
+
end.to raise_error(HashCast::Errors::UnexpectedAttributeError, "contacts[name] is not valid attribute name")
|
339
370
|
end
|
340
371
|
end
|
341
372
|
|
342
373
|
context "checking invalid parameters" do
|
343
|
-
it "should raise
|
374
|
+
it "should raise CasterNotFound exception if caster name is invalid" do
|
344
375
|
expect do
|
345
376
|
class WrongCaster
|
346
377
|
include HashCast::Caster
|
@@ -381,4 +412,29 @@ describe HashCast::Caster do
|
|
381
412
|
end.to raise_error(HashCast::Errors::CastingError, "city should be a string")
|
382
413
|
end
|
383
414
|
end
|
415
|
+
|
416
|
+
context "string caster" do
|
417
|
+
before(:all) do
|
418
|
+
class HomeCaster
|
419
|
+
include HashCast::Caster
|
420
|
+
|
421
|
+
attributes do
|
422
|
+
string :city
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
after{ HashCast.config.validate_string_null_byte = nil }
|
428
|
+
|
429
|
+
it "should allow null byte if validate_string_null_byte config is set to false" do
|
430
|
+
HashCast.config.validate_string_null_byte = false
|
431
|
+
HomeCaster.cast(city: "\u0000")
|
432
|
+
end
|
433
|
+
|
434
|
+
it "should not allow null byte if validate_string_null_byte config by default" do
|
435
|
+
expect do
|
436
|
+
HomeCaster.cast(city: "\u0000")
|
437
|
+
end.to raise_error(HashCast::Errors::CastingError, "city contains invalid characters")
|
438
|
+
end
|
439
|
+
end
|
384
440
|
end
|
@@ -4,11 +4,10 @@ describe HashCast do
|
|
4
4
|
|
5
5
|
describe ".create" do
|
6
6
|
it "should cast hash attributes" do
|
7
|
-
pending "NOT YET IMPLEMENTED"
|
8
7
|
input_hash = {
|
9
8
|
contact: {
|
10
9
|
name: "John Smith",
|
11
|
-
age:
|
10
|
+
age: 22,
|
12
11
|
company: {
|
13
12
|
name: "MyCo",
|
14
13
|
}
|
@@ -30,8 +29,9 @@ describe HashCast do
|
|
30
29
|
end
|
31
30
|
|
32
31
|
casted_hash = caster.cast(input_hash)
|
33
|
-
|
34
|
-
|
32
|
+
|
33
|
+
expect(casted_hash.object_id).to_not eq(input_hash.object_id)
|
34
|
+
expect(casted_hash).to eq(input_hash)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash_cast
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Albert Gazizov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -50,6 +50,7 @@ files:
|
|
50
50
|
- ".rspec"
|
51
51
|
- ".ruby-gemset"
|
52
52
|
- ".ruby-version"
|
53
|
+
- CHANGELOG.md
|
53
54
|
- Gemfile
|
54
55
|
- Gemfile.lock
|
55
56
|
- LICENSE.txt
|
@@ -57,7 +58,6 @@ files:
|
|
57
58
|
- Rakefile
|
58
59
|
- hash_cast.gemspec
|
59
60
|
- lib/hash_cast.rb
|
60
|
-
- lib/hash_cast/attributes_caster.rb
|
61
61
|
- lib/hash_cast/attributes_parser.rb
|
62
62
|
- lib/hash_cast/caster.rb
|
63
63
|
- lib/hash_cast/casters.rb
|
@@ -75,6 +75,7 @@ files:
|
|
75
75
|
- lib/hash_cast/config.rb
|
76
76
|
- lib/hash_cast/errors.rb
|
77
77
|
- lib/hash_cast/metadata/attribute.rb
|
78
|
+
- lib/hash_cast/recursive_caster_applicator.rb
|
78
79
|
- lib/hash_cast/version.rb
|
79
80
|
- spec/hash_cast/caster_spec.rb
|
80
81
|
- spec/hash_cast/hash_cast_spec.rb
|