hash_cast 0.5.1 → 0.5.3

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: 68e050b609ffe18b09ada29f3606534113be56827b161bae6e917cbeadc82a57
4
- data.tar.gz: 76b1ba697a03d6edcccb6f5196d3411d8af423ff5e0e0b1661b81db3e02846e6
3
+ metadata.gz: 0c7a40e91e7c2a8853eef8627131ddce5f3500bda9a3be64a9d363f65523f783
4
+ data.tar.gz: f75b041bd770043b6144bb5ec0cae0a3a52ce2103edea9e80a52b357112ab51e
5
5
  SHA512:
6
- metadata.gz: e7fc1162f788fd180c0cd1a080bb2dbbcaaeb7b28fa1d59b3d490a0bd3f744a3457998d65fc1ce6d2e9819ce898416d6832c658d8bd5e3d47486980d6a4a1db3
7
- data.tar.gz: 1d9d02707117f5619902867966c4e0a1f542e0e9aedd2b183557aaf25c6fc85704d488db2c52c22534a377779a68b1bc9d09b31420ad77af54576d701358f9fe
6
+ metadata.gz: 2503474b03a909d72daee0852aacac355da4d8837c99273336dffa403ce63d67aab3a2ea5259cb504ac096ccbd063155400ec4d47b12d72f5d1f2c5e74144536
7
+ data.tar.gz: 4d2f655abee9beaaf012dfa000518a35247612d76cb64ed0a96acd866bdcbec8cf57a25cdfc33630b68ad86500401af74fbe9e548002207e83953bb934358d9f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ **Version 0.5.3**
2
+ - Added optional support for array and string size validation
3
+
4
+ **Version 0.5.2**
5
+ - Rename AttributesCaster to HashCast::RecursiveCasterApplicator, to highlight that it's not a regular Caster
6
+
1
7
  **Version 0.5.1**
2
8
  - String caster should validate for null byte by default
3
9
  - 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.1)
4
+ hash_cast (0.5.3)
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,14 +1,20 @@
1
1
  class HashCast::Casters::ArrayCaster
2
2
 
3
3
  def self.cast(value, attr_name, options = {})
4
- if value.is_a?(Array)
5
- if options[:each]
6
- cast_array_items(value, attr_name, options)
7
- else
8
- value
4
+ unless value.is_a?(Array)
5
+ raise HashCast::Errors::CastingError, "should be an array"
6
+ end
7
+
8
+ if HashCast.config.array_size_validator_enabled
9
+ if value.size > HashCast.config.array_size_validator_limit
10
+ HashCast.config.array_size_validator_callback.call(value, attr_name, options)
9
11
  end
12
+ end
13
+
14
+ if options[:each]
15
+ cast_array_items(value, attr_name, options)
10
16
  else
11
- raise HashCast::Errors::CastingError, "should be an array"
17
+ value
12
18
  end
13
19
  end
14
20
 
@@ -8,6 +8,12 @@ class HashCast::Casters::StringCaster
8
8
  raise HashCast::Errors::CastingError, 'contains invalid characters'
9
9
  end
10
10
 
11
+ if HashCast.config.string_size_validator_enabled
12
+ if value.size > HashCast.config.string_size_validator_limit
13
+ HashCast.config.string_size_validator_callback.call(value, attr_name, options)
14
+ end
15
+ end
16
+
11
17
  casted_value
12
18
  end
13
19
 
@@ -1,5 +1,14 @@
1
1
  class HashCast::Config
2
- attr_accessor :input_keys, :output_keys, :validate_string_null_byte
2
+ attr_writer :input_keys, :output_keys, :validate_string_null_byte,
3
+ :array_size_validator_enabled,
4
+ :array_size_validator_limit,
5
+ :array_size_validator_callback,
6
+ :string_size_validator_enabled,
7
+ :string_size_validator_limit,
8
+ :string_size_validator_callback
9
+
10
+ DEFAULT_ARRAY_SIZE_VALIDATOR_LIMIT = 1000_000
11
+ DEFAULT_STRING_SIZE_VALIDATOR_LIMIT = 1000_000
3
12
 
4
13
  def input_keys
5
14
  @input_keys || :symbol
@@ -14,4 +23,48 @@ class HashCast::Config
14
23
 
15
24
  @validate_string_null_byte
16
25
  end
26
+
27
+ def array_size_validator_enabled
28
+ return false if @array_size_validator_enabled.nil?
29
+
30
+ @array_size_validator_enabled
31
+ end
32
+
33
+ def array_size_validator_limit
34
+ return DEFAULT_ARRAY_SIZE_VALIDATOR_LIMIT if @array_size_validator_limit.nil?
35
+
36
+ @array_size_validator_limit
37
+ end
38
+
39
+ def array_size_validator_callback
40
+ if @array_size_validator_callback.nil?
41
+ return lambda{ |value, name, options|
42
+ raise HashCast::Errors::CastingError, "array is too large"
43
+ }
44
+ end
45
+
46
+ @array_size_validator_callback
47
+ end
48
+
49
+ def string_size_validator_enabled
50
+ return false if @string_size_validator_enabled.nil?
51
+
52
+ @string_size_validator_enabled
53
+ end
54
+
55
+ def string_size_validator_limit
56
+ return DEFAULT_STRING_SIZE_VALIDATOR_LIMIT if @string_size_validator_limit.nil?
57
+
58
+ @string_size_validator_limit
59
+ end
60
+
61
+ def string_size_validator_callback
62
+ if @string_size_validator_callback.nil?
63
+ return lambda{ |value, name, options|
64
+ raise HashCast::Errors::CastingError, "string is too large"
65
+ }
66
+ end
67
+
68
+ @string_size_validator_callback
69
+ end
17
70
  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,7 +19,7 @@ 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
 
@@ -1,3 +1,3 @@
1
1
  module HashCast
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.3"
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 "raises error if skip_unexpected_attributes=false and 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, skip_unexpected_attributes: false)
252
- end.to raise_error(HashCast::Errors::UnexpectedAttributeError, "contact[wrong_attribute] is not valid attribute name")
253
- end
254
-
255
- it "doesn't raise unexpected attributes error by default" 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)
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"
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
+ }]
338
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
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashCast::Casters::ArrayCaster do
4
+ subject { HashCast::Casters::ArrayCaster }
5
+
6
+ it "should cast an array" do
7
+ result = subject.cast([1,2,3], :ids)
8
+ expect(result).to eq([1,2,3])
9
+ end
10
+
11
+ it "should raise an error for non-array" do
12
+ expect {
13
+ subject.cast(1, :ids)
14
+ }.to raise_error(HashCast::Errors::CastingError, "should be an array")
15
+ end
16
+
17
+ context "array size validation" do
18
+ after{
19
+ HashCast.config.array_size_validator_enabled = nil
20
+ HashCast.config.array_size_validator_limit = nil
21
+ }
22
+
23
+ it "should not raise an error for large array by default" do
24
+ result = subject.cast([1] * 10_000, :ids)
25
+ expect(result).to eq([1] * 10_000)
26
+ end
27
+
28
+ it "should raise an error for large array when validation is enabled" do
29
+ HashCast.config.array_size_validator_enabled = true
30
+ HashCast.config.array_size_validator_limit = 1000
31
+
32
+ expect {
33
+ subject.cast([1] * 10_000, :ids)
34
+ }.to raise_error(HashCast::Errors::CastingError, "array is too large")
35
+ end
36
+
37
+ it "should allow overriding the callback" do
38
+ HashCast.config.array_size_validator_enabled = true
39
+ HashCast.config.array_size_validator_limit = 1000
40
+ HashCast.config.array_size_validator_callback = lambda { |value, name, options|
41
+ raise HashCast::Errors::UnexpectedAttributeError, 'test'
42
+ }
43
+
44
+ expect {
45
+ subject.cast([1] * 10_000, :ids)
46
+ }.to raise_error(HashCast::Errors::UnexpectedAttributeError, "test")
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe HashCast::Casters::StringCaster do
4
+ subject { HashCast::Casters::StringCaster }
5
+
6
+ it "should cast a string" do
7
+ result = subject.cast("foobar", :name)
8
+ expect(result).to eq("foobar")
9
+ end
10
+
11
+ context "string size validation" do
12
+ after{
13
+ HashCast.config.string_size_validator_enabled = nil
14
+ HashCast.config.string_size_validator_limit = nil
15
+ }
16
+
17
+ it "should not raise an error for large string by default" do
18
+ result = subject.cast("a" * 10_000, :name)
19
+ expect(result).to eq("a" * 10_000)
20
+ end
21
+
22
+ it "should raise an error for large string when validation is enabled" do
23
+ HashCast.config.string_size_validator_enabled = true
24
+ HashCast.config.string_size_validator_limit = 1000
25
+
26
+ expect {
27
+ subject.cast("a" * 10_000, :ids)
28
+ }.to raise_error(HashCast::Errors::CastingError, "string is too large")
29
+ end
30
+
31
+ it "should allow overriding the callback" do
32
+ HashCast.config.string_size_validator_enabled = true
33
+ HashCast.config.string_size_validator_limit = 1000
34
+ HashCast.config.string_size_validator_callback = lambda { |value, name, options|
35
+ raise HashCast::Errors::UnexpectedAttributeError, 'test'
36
+ }
37
+
38
+ expect {
39
+ subject.cast("a" * 10_000, :ids)
40
+ }.to raise_error(HashCast::Errors::UnexpectedAttributeError, "test")
41
+ end
42
+ end
43
+ 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.1
4
+ version: 0.5.3
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-12 00:00:00.000000000 Z
11
+ date: 2025-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,7 +58,6 @@ files:
58
58
  - Rakefile
59
59
  - hash_cast.gemspec
60
60
  - lib/hash_cast.rb
61
- - lib/hash_cast/attributes_caster.rb
62
61
  - lib/hash_cast/attributes_parser.rb
63
62
  - lib/hash_cast/caster.rb
64
63
  - lib/hash_cast/casters.rb
@@ -76,8 +75,11 @@ files:
76
75
  - lib/hash_cast/config.rb
77
76
  - lib/hash_cast/errors.rb
78
77
  - lib/hash_cast/metadata/attribute.rb
78
+ - lib/hash_cast/recursive_caster_applicator.rb
79
79
  - lib/hash_cast/version.rb
80
80
  - spec/hash_cast/caster_spec.rb
81
+ - spec/hash_cast/casters/array_caster_spec.rb
82
+ - spec/hash_cast/casters/string_caster_spec.rb
81
83
  - spec/hash_cast/hash_cast_spec.rb
82
84
  - spec/spec_helper.rb
83
85
  homepage: http://github.com/droidlabs/hash_cast
@@ -105,5 +107,7 @@ specification_version: 4
105
107
  summary: Declarative Hash Caster
106
108
  test_files:
107
109
  - spec/hash_cast/caster_spec.rb
110
+ - spec/hash_cast/casters/array_caster_spec.rb
111
+ - spec/hash_cast/casters/string_caster_spec.rb
108
112
  - spec/hash_cast/hash_cast_spec.rb
109
113
  - spec/spec_helper.rb