hash_cast 0.5.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 672fe60de0d65a961d15b86a8461c967815818aad2bae13bef645bba235a6808
4
- data.tar.gz: 1831d51bdc595939bc3c61b66f72d42ce6c3d82af944c2a09b83710d1582eb5a
3
+ metadata.gz: 899228916d29fc1b30a38151b850f6734ea57cb7390561b6a3aaecd0e3e1a7e6
4
+ data.tar.gz: b055bcb17b4863fea30a1f5b3a6e739514aaff9ca853fcefb3ee702bed3ece5d
5
5
  SHA512:
6
- metadata.gz: 054603f4b2ab7fcc8b0d47ded07535bd8f550a8950515bda53753ac52d45c61b72a81162133c26e0ab2188d0415101d6612c064f2f5fe7e45fd4c50026ba756a
7
- data.tar.gz: 4c4709448714f4ff6e6deaaa5671a0c8e5de0bc54a85bb16a4647c7f2d2a9a45e69f2c01422c06c6a74f71759397d11e99a9e2357d21cf0aa0a576a3c30cdf64
6
+ metadata.gz: 1ebd62549257464c42f8d9550b5a3a592e9b84fc430619821818497d74bc1c95946effc1c21188a619834f6420b3c78fef2aac6f9859289da0ec7719188e9726
7
+ data.tar.gz: 0134f1c7695a0c205e0b9f57fa6ed995fc568e55e1e999b37d80973ac102c0450d2bde94eaf20c7c72f74f534d9da6d9432a017224d1bac4eb4b05e8b04e9f2c
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ **Version 0.5.2**
2
+ - Rename AttributesCaster to HashCast::RecursiveCasterApplicator, to highlight that it's not a regular Caster
3
+
4
+ **Version 0.5.1**
5
+ - String caster should validate for null byte by default
6
+ - skip_unexpected_attributes option is true by default
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hash_cast (0.5.0)
4
+ hash_cast (0.5.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -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
- check_attributes_defined!
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 check_attributes_defined!
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)
@@ -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::AttributesCaster
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
 
@@ -1,3 +1,3 @@
1
1
  module HashCast
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.2"
3
3
  end
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/attributes_caster'
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.should == {
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.should == {
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.should == {
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 CaterNotFound exception if caster name is invalid" do
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: "22",
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
- casted_hash.object_id.should_not == hash.object_id
34
- caster_hash.should == hash
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.0
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-10-28 00:00:00.000000000 Z
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