typed_params 1.2.7 → 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 +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +74 -3
- data/lib/typed_params/path.rb +12 -4
- data/lib/typed_params/schema.rb +12 -2
- data/lib/typed_params/types/any.rb +10 -0
- data/lib/typed_params/types/type.rb +1 -0
- data/lib/typed_params/validations/depth.rb +51 -0
- data/lib/typed_params/validator.rb +2 -2
- data/lib/typed_params/version.rb +1 -1
- data/lib/typed_params.rb +16 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aedcf15896c7a9b3975e1d8fcd5ec05752120f813ff92707fa9c7684fdb77f64
|
4
|
+
data.tar.gz: 1593452e1eef49a1ec497f9118ff3f08f3902ed117d9785f4c0f54b3bcf417b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fad33f1bac6f33d40487e88fab1bba065223e9986c3cf96fc1dc2654d87b2cbc2b59d82af073ca833a71e5d953eab6793f43f2b8b4bc698e21d21e50c544b96b
|
7
|
+
data.tar.gz: 2b0b082f223b1a7b6fd31d1f2a8b4f86364a117cf4efb1893d31a3c395a276ceaf0797481b9958679780c3b1814d62c842b3e696ff605a9b4bd89d4e52ed2649
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.4.0
|
4
|
+
|
5
|
+
- Add `depth:` validator for asserting maximum depth of `hash` and `array` params.
|
6
|
+
|
7
|
+
## 1.3.0
|
8
|
+
|
9
|
+
- Add `any` type to skip type validation for a given param.
|
10
|
+
|
3
11
|
## 1.2.7
|
4
12
|
|
5
13
|
- Fix issue where the JSONAPI formatter did not ignore `meta` when `data` is omitted.
|
data/README.md
CHANGED
@@ -68,6 +68,7 @@ Links:
|
|
68
68
|
- [Non-scalar types](#non-scalar-types)
|
69
69
|
- [Custom types](#custom-types)
|
70
70
|
- [Formats](#formats)
|
71
|
+
- [Custom formats](#custom-formats)
|
71
72
|
- [Contributing](#contributing)
|
72
73
|
- [License](#license)
|
73
74
|
|
@@ -447,6 +448,7 @@ Parameters can have validations, transforms, and more.
|
|
447
448
|
- [`:exclusion`](#exclusion-validation)
|
448
449
|
- [`:format`](#format-validation)
|
449
450
|
- [`:length`](#length-validation)
|
451
|
+
- [`:depth`](#depth-validation)
|
450
452
|
- [`:transform`](#transform-parameter)
|
451
453
|
- [`:validate`](#validate-parameter)
|
452
454
|
- [`:polymorphic`](#polymorphic-parameter)
|
@@ -579,15 +581,21 @@ error.
|
|
579
581
|
|
580
582
|
#### Allow non-scalars
|
581
583
|
|
582
|
-
Only applicable to the `:hash`
|
583
|
-
a `:hash`
|
584
|
+
Only applicable to the `:hash` and `:array` types, and their subtypes. Allow
|
585
|
+
non-scalar values in a `:hash` or `:array` parameter.
|
584
586
|
|
585
587
|
```ruby
|
588
|
+
# Allow unlimited nesting
|
586
589
|
param :metadata, type: :hash, allow_non_scalars: true
|
590
|
+
|
591
|
+
# Disallow non-scalars
|
592
|
+
param :data, type: :hash, allow_non_scalars: false
|
587
593
|
```
|
588
594
|
|
589
595
|
By default, non-scalar parameters are rejected with a `TypedParams::InvalidParameterError`
|
590
|
-
error.
|
596
|
+
error. Use [`:depth`](#depth-validation) to control maximum depth.
|
597
|
+
|
598
|
+
Scalar types can be found under [Types](#scalar-types).
|
591
599
|
|
592
600
|
#### Nilify blanks
|
593
601
|
|
@@ -638,6 +646,20 @@ param :odd, type: :string, length: { in: [2, 4, 6, 8] }
|
|
638
646
|
param :ten, type: :string, length: { is: 10 }
|
639
647
|
```
|
640
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
|
+
|
641
663
|
#### Transform parameter
|
642
664
|
|
643
665
|
Transform the parameter using a lambda. This is commonly used to transform a
|
@@ -780,6 +802,7 @@ Type `:date`. Defines a date parameter. Must be a `Date`.
|
|
780
802
|
|
781
803
|
- [`:array`](#array-type)
|
782
804
|
- [`:hash`](#hash-type)
|
805
|
+
- [`:any`](#any-type)
|
783
806
|
|
784
807
|
#### Array type
|
785
808
|
|
@@ -819,6 +842,15 @@ end
|
|
819
842
|
# non-schema hash
|
820
843
|
param :only_scalars, type: :hash
|
821
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
|
822
854
|
```
|
823
855
|
|
824
856
|
### Custom types
|
@@ -944,6 +976,45 @@ class UsersController < ApplicationController
|
|
944
976
|
end
|
945
977
|
```
|
946
978
|
|
979
|
+
### Custom formats
|
980
|
+
|
981
|
+
You may register custom formatters that can be utilized in your schemas.
|
982
|
+
|
983
|
+
Each formatter consists of, at minimum, a `transform:` lambda, accepting a
|
984
|
+
params hash as well as optional `controller:` and `schema:` keywords, and
|
985
|
+
returning the formatted params.
|
986
|
+
|
987
|
+
For more usage examples, see [the default formatters](https://github.com/keygen-sh/typed_params/tree/master/lib/typed_params/formatters).
|
988
|
+
|
989
|
+
```rb
|
990
|
+
TypedParams::Formatters.register(:strong_params,
|
991
|
+
transform: -> (params, controller:) {
|
992
|
+
wrapper = controller.controller_name.singularize.to_sym
|
993
|
+
unwrapped = params[wrapper]
|
994
|
+
|
995
|
+
ActionController::Parameters.new(unwrapped).permit!
|
996
|
+
},
|
997
|
+
)
|
998
|
+
```
|
999
|
+
|
1000
|
+
```rb
|
1001
|
+
typed_params {
|
1002
|
+
format :strong_params
|
1003
|
+
|
1004
|
+
param :user, type: :hash do
|
1005
|
+
param :email, type: :string, format: { with: /@/ }
|
1006
|
+
param :password, type: :string
|
1007
|
+
end
|
1008
|
+
}
|
1009
|
+
def create
|
1010
|
+
puts user_params
|
1011
|
+
# => #<ActionController::Parameters
|
1012
|
+
# {"email"=>"json@smith.example","password"=>"7c84241a1102"}
|
1013
|
+
# permitted: true
|
1014
|
+
# >
|
1015
|
+
end
|
1016
|
+
```
|
1017
|
+
|
947
1018
|
## Is it any good?
|
948
1019
|
|
949
1020
|
[Yes.](https://news.ycombinator.com/item?id=3067434)
|
data/lib/typed_params/path.rb
CHANGED
@@ -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 <<
|
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
|
data/lib/typed_params/schema.rb
CHANGED
@@ -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?
|
@@ -281,7 +290,8 @@ module TypedParams
|
|
281
290
|
def unless? = !@unless.nil?
|
282
291
|
def array? = Types.array?(type)
|
283
292
|
def hash? = Types.hash?(type)
|
284
|
-
def scalar? =
|
293
|
+
def scalar? = type.scalar?
|
294
|
+
def any? = type.any?
|
285
295
|
def formatter? = !!@formatter
|
286
296
|
|
287
297
|
def inspect
|
@@ -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
|
@@ -28,7 +28,7 @@ module TypedParams
|
|
28
28
|
|
29
29
|
# Assert type
|
30
30
|
raise InvalidParameterError.new("type mismatch (received #{type.humanize} expected #{schema.type.humanize})", path: param.path, source: schema.source) unless
|
31
|
-
type == schema.type || type.subtype? && type.archetype == schema.type
|
31
|
+
schema.any? || type == schema.type || type.subtype? && type.archetype == schema.type
|
32
32
|
|
33
33
|
# Assertions for params without children
|
34
34
|
if schema.children.nil?
|
@@ -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
|
data/lib/typed_params/version.rb
CHANGED
data/lib/typed_params.rb
CHANGED
@@ -26,6 +26,7 @@ require 'typed_params/transforms/noop'
|
|
26
26
|
require 'typed_params/transforms/transform'
|
27
27
|
require 'typed_params/transformer'
|
28
28
|
require 'typed_params/types'
|
29
|
+
require 'typed_params/types/any'
|
29
30
|
require 'typed_params/types/array'
|
30
31
|
require 'typed_params/types/boolean'
|
31
32
|
require 'typed_params/types/date'
|
@@ -39,6 +40,7 @@ require 'typed_params/types/string'
|
|
39
40
|
require 'typed_params/types/symbol'
|
40
41
|
require 'typed_params/types/time'
|
41
42
|
require 'typed_params/types/type'
|
43
|
+
require 'typed_params/validations/depth'
|
42
44
|
require 'typed_params/validations/exclusion'
|
43
45
|
require 'typed_params/validations/format'
|
44
46
|
require 'typed_params/validations/inclusion'
|
@@ -58,9 +60,22 @@ module TypedParams
|
|
58
60
|
|
59
61
|
class UndefinedActionError < StandardError; end
|
60
62
|
class InvalidMethodError < StandardError; end
|
61
|
-
class ValidationError < StandardError; end
|
62
63
|
class CoercionError < StandardError; end
|
63
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
|
+
|
64
79
|
class InvalidParameterError < StandardError
|
65
80
|
attr_reader :source,
|
66
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.
|
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-
|
11
|
+
date: 2025-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -79,6 +79,7 @@ files:
|
|
79
79
|
- lib/typed_params/transforms/noop.rb
|
80
80
|
- lib/typed_params/transforms/transform.rb
|
81
81
|
- lib/typed_params/types.rb
|
82
|
+
- lib/typed_params/types/any.rb
|
82
83
|
- lib/typed_params/types/array.rb
|
83
84
|
- lib/typed_params/types/boolean.rb
|
84
85
|
- lib/typed_params/types/date.rb
|
@@ -92,6 +93,7 @@ files:
|
|
92
93
|
- lib/typed_params/types/symbol.rb
|
93
94
|
- lib/typed_params/types/time.rb
|
94
95
|
- lib/typed_params/types/type.rb
|
96
|
+
- lib/typed_params/validations/depth.rb
|
95
97
|
- lib/typed_params/validations/exclusion.rb
|
96
98
|
- lib/typed_params/validations/format.rb
|
97
99
|
- lib/typed_params/validations/inclusion.rb
|