typed_params 1.3.0 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6db30cea880bd70a7a4c6ceeae65ebb28806ab5b62e0271ef9a660fbc88f394
4
- data.tar.gz: 965eaf3101f70f84c6aae336ade54700580c0b3a850cb3c9813d61a3f1f7c23d
3
+ metadata.gz: aedcf15896c7a9b3975e1d8fcd5ec05752120f813ff92707fa9c7684fdb77f64
4
+ data.tar.gz: 1593452e1eef49a1ec497f9118ff3f08f3902ed117d9785f4c0f54b3bcf417b8
5
5
  SHA512:
6
- metadata.gz: 0d119eb25721f1f31a094e2d0c303af91ff9643a93d14c935c2a46c94efac99c614f72361c7e1ad8760ab7c0d1412486ce460dbd33e3e12293cc85e74a679362
7
- data.tar.gz: b77c97d4355ea181408702e5a240f4ce99edbf232c1bb7c924d70a1344945ede034fded66c87b6ce055e1bd579f2be8758dc97f1d2e2023611e003566906121d
6
+ metadata.gz: fad33f1bac6f33d40487e88fab1bba065223e9986c3cf96fc1dc2654d87b2cbc2b59d82af073ca833a71e5d953eab6793f43f2b8b4bc698e21d21e50c544b96b
7
+ data.tar.gz: 2b0b082f223b1a7b6fd31d1f2a8b4f86364a117cf4efb1893d31a3c395a276ceaf0797481b9958679780c3b1814d62c842b3e696ff605a9b4bd89d4e52ed2649
data/CHANGELOG.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.0
4
+
5
+ - Add `depth:` validator for asserting maximum depth of `hash` and `array` params.
6
+
3
7
  ## 1.3.0
4
8
 
5
- - Add `:any` type to skip type validation for a given param.
9
+ - Add `any` type to skip type validation for a given param.
6
10
 
7
11
  ## 1.2.7
8
12
 
data/README.md CHANGED
@@ -448,6 +448,7 @@ Parameters can have validations, transforms, and more.
448
448
  - [`:exclusion`](#exclusion-validation)
449
449
  - [`:format`](#format-validation)
450
450
  - [`:length`](#length-validation)
451
+ - [`:depth`](#depth-validation)
451
452
  - [`:transform`](#transform-parameter)
452
453
  - [`:validate`](#validate-parameter)
453
454
  - [`:polymorphic`](#polymorphic-parameter)
@@ -580,15 +581,21 @@ error.
580
581
 
581
582
  #### Allow non-scalars
582
583
 
583
- Only applicable to the `:hash` type and its subtypes. Allow non-scalar values in
584
- a `:hash` parameter. Scalar types can be found under [Types](#scalar-types).
584
+ Only applicable to the `:hash` and `:array` types, and their subtypes. Allow
585
+ non-scalar values in a `:hash` or `:array` parameter.
585
586
 
586
587
  ```ruby
588
+ # Allow unlimited nesting
587
589
  param :metadata, type: :hash, allow_non_scalars: true
590
+
591
+ # Disallow non-scalars
592
+ param :data, type: :hash, allow_non_scalars: false
588
593
  ```
589
594
 
590
595
  By default, non-scalar parameters are rejected with a `TypedParams::InvalidParameterError`
591
- error.
596
+ error. Use [`:depth`](#depth-validation) to control maximum depth.
597
+
598
+ Scalar types can be found under [Types](#scalar-types).
592
599
 
593
600
  #### Nilify blanks
594
601
 
@@ -639,6 +646,20 @@ param :odd, type: :string, length: { in: [2, 4, 6, 8] }
639
646
  param :ten, type: :string, length: { is: 10 }
640
647
  ```
641
648
 
649
+ #### Depth validation
650
+
651
+ The parameter must be a certain depth.
652
+
653
+ ```ruby
654
+ # Allow nested objects up to 2 levels deep
655
+ param :config, type: :hash, depth: { maximum: 2 }
656
+
657
+ # Allow nested arrays up to 3 levels deep
658
+ param :data, type: :array, depth: { maximum: 3 }
659
+ ```
660
+
661
+ Only applicable for `:hash` and `:array` types. Automatically sets `allow_non_scalars: true`.
662
+
642
663
  #### Transform parameter
643
664
 
644
665
  Transform the parameter using a lambda. This is commonly used to transform a
@@ -781,6 +802,7 @@ Type `:date`. Defines a date parameter. Must be a `Date`.
781
802
 
782
803
  - [`:array`](#array-type)
783
804
  - [`:hash`](#hash-type)
805
+ - [`:any`](#any-type)
784
806
 
785
807
  #### Array type
786
808
 
@@ -820,6 +842,15 @@ end
820
842
  # non-schema hash
821
843
  param :only_scalars, type: :hash
822
844
  param :non_scalars_too, type: :hash, allow_non_scalars: true
845
+ param :limited_nesting, type: :hash, depth: { maximum: 2 }
846
+ ```
847
+
848
+ #### Any type
849
+
850
+ Type `:any`. Defines a parameter that is not type checked.
851
+
852
+ ```ruby
853
+ param :anything_goes, type: :any
823
854
  ```
824
855
 
825
856
  ### Custom types
@@ -12,21 +12,29 @@ module TypedParams
12
12
  def to_json_pointer = '/' + keys.map { transform_key(_1) }.join('/')
13
13
  def to_dot_notation = keys.map { transform_key(_1) }.join('.')
14
14
 
15
+ def +(other)
16
+ raise ArgumentError, 'must be a Path object or nil' unless other in Path | nil
17
+
18
+ return self if
19
+ other.nil?
20
+
21
+ Path.new(*keys, *other.keys, casing:)
22
+ end
23
+
15
24
  def to_s
16
25
  keys.map { transform_key(_1) }.reduce(+'') do |s, key|
17
- next s << key if s.blank?
18
-
19
26
  case key
20
27
  when Integer
21
28
  s << "[#{key}]"
22
29
  else
23
- s << ".#{key}"
30
+ s << '.' unless s.blank?
31
+ s << key.to_s
24
32
  end
25
33
  end
26
34
  end
27
35
 
28
36
  def inspect
29
- "#<#{self.class.name}: #{to_s.inspect}>"
37
+ "#<#{self.class.name}: #{to_s.inspect} keys=#{keys.inspect} casing=#{casing.inspect}>"
30
38
  end
31
39
 
32
40
  private
@@ -34,6 +34,7 @@ module TypedParams
34
34
  exclusion: nil,
35
35
  format: nil,
36
36
  length: nil,
37
+ depth: nil,
37
38
  transform: nil,
38
39
  validate: nil,
39
40
  if: nil,
@@ -76,6 +77,9 @@ module TypedParams
76
77
  length.key?(:is)
77
78
  )
78
79
 
80
+ raise ArgumentError, 'depth must be a hash with :maximum key' unless
81
+ depth.nil? || depth.is_a?(Hash) && depth.key?(:maximum)
82
+
79
83
  @controller = controller
80
84
  @source = source
81
85
  @type = Types[type]
@@ -88,14 +92,15 @@ module TypedParams
88
92
  @coerce = coerce && @type.coercable?
89
93
  @polymorphic = polymorphic
90
94
  @allow_blank = key == ROOT || allow_blank
95
+ @allow_non_scalars = allow_non_scalars || depth.present?
91
96
  @allow_nil = allow_nil
92
- @allow_non_scalars = allow_non_scalars
93
97
  @nilify_blanks = nilify_blanks
94
98
  @noop = noop
95
99
  @inclusion = inclusion
96
100
  @exclusion = exclusion
97
101
  @format = format
98
102
  @length = length
103
+ @depth = depth
99
104
  @casing = casing
100
105
  @transform = transform
101
106
  @children = nil
@@ -119,6 +124,9 @@ module TypedParams
119
124
  @validations << Validations::Length.new(length) if
120
125
  length.present?
121
126
 
127
+ @validations << Validations::Depth.new(depth) if
128
+ depth.present?
129
+
122
130
  @validations << Validations::Validation.wrap(validate) if
123
131
  validate.present?
124
132
 
@@ -261,6 +269,7 @@ module TypedParams
261
269
  end
262
270
  end
263
271
 
272
+
264
273
  def root? = key == ROOT
265
274
  def child? = !root?
266
275
  def children? = !children.blank?
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typed_params/validations/validation'
4
+ require 'typed_params/path'
5
+
6
+ module TypedParams
7
+ module Validations
8
+ class Depth < Validation
9
+ def call(value)
10
+ case options
11
+ in maximum: Integer => maximum_depth
12
+ return if
13
+ maximum_depth.nil? || maximum_depth == Float::INFINITY
14
+
15
+ validate_depth!(value, maximum_depth:)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def validate_depth!(current_value, maximum_depth:, current_depth: 0, current_path: [])
22
+ case current_value
23
+ in Hash => hash
24
+ hash.each do |key, value|
25
+ path = current_path + [key]
26
+ next if
27
+ Types.scalar?(value)
28
+
29
+ raise ValidationError.new("maximum depth of #{maximum_depth} exceeded", path: Path.new(*path)) if
30
+ current_depth >= maximum_depth
31
+
32
+ validate_depth!(value, maximum_depth:, current_depth: current_depth + 1, current_path: path)
33
+ end
34
+ in Array => arr
35
+ arr.each_with_index do |value, index|
36
+ path = current_path + [index]
37
+ next if
38
+ Types.scalar?(value)
39
+
40
+ raise ValidationError.new("maximum depth of #{maximum_depth} exceeded", path: Path.new(*path)) if
41
+ current_depth >= maximum_depth
42
+
43
+ validate_depth!(value, maximum_depth:, current_depth: current_depth + 1, current_path: path)
44
+ end
45
+ else
46
+ # TODO(ezekg) add support for custom types e.g. enumerables?
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -67,7 +67,7 @@ module TypedParams
67
67
  schema.validations.each do |validation|
68
68
  validation.call(param.value)
69
69
  rescue ValidationError => e
70
- raise InvalidParameterError.new(e.message, path: param.path, source: schema.source)
70
+ raise InvalidParameterError.new(e.message, path: param.path + e.path, source: schema.source)
71
71
  end
72
72
  end
73
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypedParams
4
- VERSION = '1.3.0'
4
+ VERSION = '1.4.0'
5
5
  end
data/lib/typed_params.rb CHANGED
@@ -40,6 +40,7 @@ require 'typed_params/types/string'
40
40
  require 'typed_params/types/symbol'
41
41
  require 'typed_params/types/time'
42
42
  require 'typed_params/types/type'
43
+ require 'typed_params/validations/depth'
43
44
  require 'typed_params/validations/exclusion'
44
45
  require 'typed_params/validations/format'
45
46
  require 'typed_params/validations/inclusion'
@@ -59,9 +60,22 @@ module TypedParams
59
60
 
60
61
  class UndefinedActionError < StandardError; end
61
62
  class InvalidMethodError < StandardError; end
62
- class ValidationError < StandardError; end
63
63
  class CoercionError < StandardError; end
64
64
 
65
+ class ValidationError < StandardError
66
+ attr_reader :path
67
+
68
+ def initialize(message, path: nil)
69
+ @path = path
70
+
71
+ super(message)
72
+ end
73
+
74
+ def inspect
75
+ "#<#{self.class.name} message=#{message.inspect} path=#{path.inspect}>"
76
+ end
77
+ end
78
+
65
79
  class InvalidParameterError < StandardError
66
80
  attr_reader :source,
67
81
  :path
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typed_params
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zeke Gabrielse
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-02 00:00:00.000000000 Z
11
+ date: 2025-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -93,6 +93,7 @@ files:
93
93
  - lib/typed_params/types/symbol.rb
94
94
  - lib/typed_params/types/time.rb
95
95
  - lib/typed_params/types/type.rb
96
+ - lib/typed_params/validations/depth.rb
96
97
  - lib/typed_params/validations/exclusion.rb
97
98
  - lib/typed_params/validations/format.rb
98
99
  - lib/typed_params/validations/inclusion.rb