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 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