typed_params 1.4.1 → 1.5.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: f3db097654cd261a776ba35201bcf702180ed68f3010076ff7ef0482aee7b459
4
- data.tar.gz: 55ba5af0bd740da0b08bda6b40ae39734e16bc1145d1d335a922b439d6ac65de
3
+ metadata.gz: 4fb4d755ba896e8e9e9e410c7588f6135a3d0b88ed937d53afebd9df1bdf39d0
4
+ data.tar.gz: '08dfb1ac8f89635c086a020b105d44707801f7ebdf62bd41a034707a70fe2f53'
5
5
  SHA512:
6
- metadata.gz: 466aced438f27915ffbb26005090ac5caa85b801491d3e426eb8af86cb863c8504cf0e36d3ae1b1f3b3f629c3ff1a4e5861a78fc3a48edc5bd94778831097114
7
- data.tar.gz: f571e30f4d4dfef9bb83d35c095901814d3301a887779fc36b93a67f885d5835ca52a7f48de21c65bbc82544b6253556c8933b6450a3ee3890e9704afe562fdf
6
+ metadata.gz: b21341151c1dcd66bd8cfbd5609bd0fc1855063cb76ca05c8383cf323bbd25358c4830536b93c6073b3ec4c84423c83a6a9a1128f33aaca85d58dd91a16485f0
7
+ data.tar.gz: e9b31286a02ab55b93dda164827bec0bb89dc6182a3eedd8aa8ceb2f066a501274516b6b809c28f1d2066b8f4fe3e81b48401b39c667852fe9f2abd17afd3218
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.0
4
+
5
+ - Add `collapse:` keyword to collapse a nested hash param into its parent. Accepts `true`, a hash with `format:` (`:parent_child`, `:child_parent`, or `:child`), or a custom Proc.
6
+ - Add support for custom `Transform` classes in `transform:` keyword. Now accepts either a Proc or a `Transforms::Transform` subclass instance.
7
+ - Add support for custom `Validation` classes in `validate:` keyword. Now accepts either a Proc or a `Validations::Validation` subclass instance.
8
+
3
9
  ## 1.4.1
4
10
 
5
11
  - Remove deprecated `ActiveSupport::Configurable`.
data/README.md CHANGED
@@ -451,6 +451,7 @@ Parameters can have validations, transforms, and more.
451
451
  - [`:depth`](#depth-validation)
452
452
  - [`:transform`](#transform-parameter)
453
453
  - [`:validate`](#validate-parameter)
454
+ - [`:collapse`](#collapse-parameter)
454
455
  - [`:polymorphic`](#polymorphic-parameter)
455
456
 
456
457
  #### Parameter key
@@ -662,8 +663,8 @@ Only applicable for `:hash` and `:array` types. Automatically sets `allow_non_sc
662
663
 
663
664
  #### Transform parameter
664
665
 
665
- Transform the parameter using a lambda. This is commonly used to transform a
666
- parameter into a nested attributes hash or array.
666
+ Transform the parameter using a lambda or custom transform class. This is
667
+ commonly used to transform a parameter into a nested attributes hash or array.
667
668
 
668
669
  ```ruby
669
670
  param :role, type: :string, transform: -> _, name {
@@ -674,7 +675,20 @@ param :role, type: :string, transform: -> _, name {
674
675
  The lambda must accept a key (the current parameter key), and a value (the
675
676
  current parameter value).
676
677
 
677
- The lambda must return a tuple with the new key and value.
678
+ The lambda must return a tuple with the new key and value. Return `nil` for
679
+ the key to delete the parameter.
680
+
681
+ You can also use a custom transform class:
682
+
683
+ ```ruby
684
+ class UpcaseTransform < TypedParams::Transforms::Transform
685
+ def call(param)
686
+ param.value = param.value.upcase
687
+ end
688
+ end
689
+
690
+ param :name, type: :string, transform: UpcaseTransform.new
691
+ ```
678
692
 
679
693
  #### Validate parameter
680
694
 
@@ -709,6 +723,62 @@ param :invalid, type: :string, validate: -> v {
709
723
  }
710
724
  ```
711
725
 
726
+ You can also use a custom validation class:
727
+
728
+ ```ruby
729
+ class PalindromeValidation < TypedParams::Validations::Validation
730
+ def call(value)
731
+ raise TypedParams::ValidationError, 'must be a palindrome' unless value == value.reverse
732
+ end
733
+ end
734
+
735
+ param :word, type: :string, validate: PalindromeValidation.new(nil)
736
+ ```
737
+
738
+ #### Collapse parameter
739
+
740
+ Collapse a nested hash parameter into its parent. This is useful for flattening
741
+ nested structures.
742
+
743
+ ```ruby
744
+ param :date, type: :hash, collapse: true do
745
+ param :start, type: :string
746
+ param :end, type: :string
747
+ end
748
+
749
+ typed_params # => { date_start:, date_end: }
750
+ ```
751
+
752
+ The `:collapse` option accepts:
753
+
754
+ - `true` - use the default `:parent_child` format
755
+ - `{ format: :parent_child }` - prefix with parent key (e.g. `date_start`)
756
+ - `{ format: :child_parent }` - suffix with parent key (e.g. `start_date`)
757
+ - `{ format: :child }` - use child key only (e.g. `start`)
758
+ - A custom Proc for full control
759
+
760
+ ```ruby
761
+ param :date, type: :hash, collapse: { format: :child_parent } do
762
+ param :start, type: :string
763
+ param :end, type: :string
764
+ end
765
+
766
+ typed_params # => { start_date:, end_date: }
767
+
768
+ param :resource, type: :hash, collapse: { format: :child } do
769
+ param :type, type: :string
770
+ param :id, type: :string
771
+ end
772
+
773
+ typed_params # => { type:, id: }
774
+
775
+ # Custom Procs receive parent_key, parent_value, child_key, child_value
776
+ param :resource, type: :hash, collapse: -> (pk, pv, ck, cv) { [:"#{pk}-#{ck}", cv] } do
777
+ param :type, type: :string
778
+ param :id, type: :string
779
+ end
780
+ ```
781
+
712
782
  #### Polymorphic parameter
713
783
 
714
784
  _Note: currently, this option is only utilized by the JSONAPI formatter._
@@ -4,17 +4,19 @@ require 'typed_params/path'
4
4
 
5
5
  module TypedParams
6
6
  class Parameter
7
- attr_accessor :key,
8
- :value
7
+ attr_accessor :value
9
8
 
10
- attr_reader :schema,
11
- :parent
9
+ attr_reader :key,
10
+ :schema,
11
+ :parent,
12
+ :deleted
12
13
 
13
14
  def initialize(key:, value:, schema:, parent: nil)
14
- @key = key
15
- @value = value
16
- @schema = schema
17
- @parent = parent
15
+ @key = key
16
+ @value = value
17
+ @schema = schema
18
+ @parent = parent
19
+ @deleted = false
18
20
  end
19
21
 
20
22
  def array? = Types.array?(value)
@@ -48,10 +50,20 @@ module TypedParams
48
50
  end
49
51
  end
50
52
 
53
+ def deleted? = !!deleted
51
54
  def delete
52
55
  raise NotImplementedError, "cannot delete param: #{key.inspect}" unless
53
56
  parent?
54
57
 
58
+ @deleted = true
59
+
60
+ pop
61
+ end
62
+
63
+ def pop
64
+ raise NotImplementedError, "cannot pop param: #{key.inspect}" unless
65
+ parent?
66
+
55
67
  case parent.value
56
68
  when Array
57
69
  parent.value.delete(self)
@@ -62,6 +74,18 @@ module TypedParams
62
74
  end
63
75
  end
64
76
 
77
+ def rename(new_key)
78
+ return if
79
+ key == new_key
80
+
81
+ if parent?
82
+ parent[new_key] = pop # move self
83
+ end
84
+
85
+ @key = new_key
86
+ end
87
+ alias :key= :rename
88
+
65
89
  def unwrap(formatter: schema.formatter, controller: nil)
66
90
  v = case value
67
91
  when Hash
@@ -41,6 +41,7 @@ module TypedParams
41
41
  unless: nil,
42
42
  as: nil,
43
43
  alias: nil,
44
+ collapse: nil,
44
45
  casing: TypedParams.config.key_transform,
45
46
  &block
46
47
  )
@@ -80,6 +81,9 @@ module TypedParams
80
81
  raise ArgumentError, 'depth must be a hash with :maximum key' unless
81
82
  depth.nil? || depth.is_a?(Hash) && depth.key?(:maximum)
82
83
 
84
+ raise ArgumentError, 'collapse must be true, a hash, or a proc' unless
85
+ collapse.nil? || collapse == true || collapse.is_a?(Hash) || collapse.is_a?(Proc)
86
+
83
87
  @controller = controller
84
88
  @source = source
85
89
  @type = Types[type]
@@ -103,6 +107,8 @@ module TypedParams
103
107
  @depth = depth
104
108
  @casing = casing
105
109
  @transform = transform
110
+ @validate = validate
111
+ @collapse = collapse
106
112
  @children = nil
107
113
  @if = binding.local_variable_get(:if)
108
114
  @unless = binding.local_variable_get(:unless)
@@ -142,6 +148,9 @@ module TypedParams
142
148
  @transforms << Transforms::Transform.wrap(transform) if
143
149
  transform.present?
144
150
 
151
+ @transforms << Transforms::Collapse.new(collapse) if
152
+ collapse.present?
153
+
145
154
  @transforms << Transforms::KeyCasing.new(casing) if
146
155
  casing.present?
147
156
 
@@ -152,8 +161,11 @@ module TypedParams
152
161
  @type.nil?
153
162
 
154
163
  if block_given?
155
- raise ArgumentError, "type #{@type} does not accept a block" if
156
- @type.present? && !@type.accepts_block?
164
+ raise ArgumentError, "type #{@type} does not accept a block" unless
165
+ @type.accepts_block?
166
+
167
+ raise ArgumentError, "type #{@type} with a block cannot be coerced" if
168
+ @coerce
157
169
 
158
170
  @children = case
159
171
  when Types.array?(@type)
@@ -292,6 +304,7 @@ module TypedParams
292
304
  def scalar? = type.scalar?
293
305
  def any? = type.any?
294
306
  def formatter? = !!@formatter
307
+ def collapse? = !!@collapse
295
308
 
296
309
  def inspect
297
310
  "#<#{self.class.name} key=#{key.inspect} type=#{type.inspect} children=#{children.inspect}>"
@@ -7,7 +7,6 @@ module TypedParams
7
7
  def call(params)
8
8
  depth_first_map(params) do |param|
9
9
  schema = param.schema
10
- parent = param.parent
11
10
 
12
11
  # Ignore nil optionals when config is enabled
13
12
  unless schema.allow_nil?
@@ -18,30 +17,19 @@ module TypedParams
18
17
  end
19
18
  end
20
19
 
21
- schema.transforms.map do |transform|
22
- key, value = transform.call(param.key, param.value)
23
- if key.nil?
24
- param.delete
25
-
26
- break
27
- end
20
+ schema.transforms.each do |transform|
21
+ transform.call(param)
22
+ break if
23
+ param.deleted?
28
24
 
29
25
  # Check for nils again after transform
30
26
  unless schema.allow_nil?
31
- if value.nil? && schema.optional? && TypedParams.config.ignore_nil_optionals
27
+ if param.value.nil? && schema.optional? && TypedParams.config.ignore_nil_optionals
32
28
  param.delete
33
29
 
34
30
  break
35
31
  end
36
32
  end
37
-
38
- # If param's key has changed, we want to rename the key
39
- # for its parent too.
40
- if param.parent? && param.key != key
41
- parent[key] = param.delete
42
- end
43
-
44
- param.key, param.value = key, value
45
33
  end
46
34
  end
47
35
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typed_params/transforms/transform'
4
+
5
+ module TypedParams
6
+ module Transforms
7
+ class Collapse < Transform
8
+ DEFAULT_FORMAT = :parent_child
9
+
10
+ def initialize(options)
11
+ @format = case options
12
+ in format: :parent_child | :child_parent | :child => format
13
+ format
14
+ in Proc => transformer
15
+ transformer
16
+ in true
17
+ DEFAULT_FORMAT
18
+ end
19
+ end
20
+
21
+ def call(param)
22
+ return unless
23
+ param.hash? && param.parent?
24
+
25
+ parent = param.parent
26
+
27
+ param.values.each do |child|
28
+ key, value = transform(param.key, param.value, child.key, child.value)
29
+
30
+ child.key = key
31
+ child.value = value
32
+
33
+ parent[key] = child # move up
34
+ end
35
+
36
+ param.delete
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :format
42
+
43
+ def transform(parent_key, parent_value, child_key, child_value)
44
+ case format
45
+ in Proc
46
+ format.call(parent_key, parent_value, child_key, child_value)
47
+ in :parent_child
48
+ [:"#{parent_key}_#{child_key}", child_value]
49
+ in :child_parent
50
+ [:"#{child_key}_#{parent_key}", child_value]
51
+ in :child
52
+ [child_key, child_value]
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -5,12 +5,15 @@ require 'typed_params/transforms/transform'
5
5
  module TypedParams
6
6
  module Transforms
7
7
  class KeyAlias < Transform
8
- def initialize(as) = @as = as
9
- def call(_, value) = [as, value]
8
+ def initialize(key) = @key = key
9
+
10
+ def call(param)
11
+ param.key = key
12
+ end
10
13
 
11
14
  private
12
15
 
13
- attr_reader :as
16
+ attr_reader :key
14
17
  end
15
18
  end
16
19
  end
@@ -7,11 +7,15 @@ module TypedParams
7
7
  class KeyCasing < Transform
8
8
  def initialize(casing) = @casing = casing
9
9
 
10
- def call(key, value)
11
- transformed_key = transform_key(key)
12
- transformed_value = transform_value(value)
10
+ def call(param)
11
+ return if
12
+ casing.nil?
13
13
 
14
- [transformed_key, transformed_value]
14
+ key = transform_key(param.key)
15
+ value = transform_value(param.value)
16
+
17
+ param.key = key unless key == param.key
18
+ param.value = value
15
19
  end
16
20
 
17
21
  private
@@ -5,11 +5,11 @@ require 'typed_params/transforms/transform'
5
5
  module TypedParams
6
6
  module Transforms
7
7
  class NilifyBlanks < Transform
8
- def call(key, value)
9
- return [key, value] if
10
- value.is_a?(Array) || value.is_a?(Hash)
8
+ def call(param)
9
+ return if
10
+ param.value.is_a?(Array) || param.value.is_a?(Hash)
11
11
 
12
- [key, value.blank? ? nil : value]
12
+ param.value = nil if param.value.blank?
13
13
  end
14
14
  end
15
15
  end
@@ -5,7 +5,9 @@ require 'typed_params/transforms/transform'
5
5
  module TypedParams
6
6
  module Transforms
7
7
  class Noop < Transform
8
- def call(*) = []
8
+ def call(param)
9
+ param.delete
10
+ end
9
11
  end
10
12
  end
11
13
  end
@@ -3,9 +3,14 @@
3
3
  module TypedParams
4
4
  module Transforms
5
5
  class Transform
6
- def call(key, value) = raise NotImplementedError
6
+ def call(param) = raise NotImplementedError
7
7
 
8
- def self.wrap(fn) = fn
8
+ # wraps a callable e.g. Proc for use as a Transform
9
+ def self.wrap(fn)
10
+ return fn if fn in Transform
11
+
12
+ Wrapped.new(fn)
13
+ end
9
14
  end
10
15
  end
11
16
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typed_params/transforms/transform'
4
+
5
+ module TypedParams
6
+ module Transforms
7
+ class Wrapped < Transform
8
+ def initialize(fn) = @fn = fn
9
+
10
+ def call(param)
11
+ key, value = fn.call(param.key, param.value)
12
+ if key.nil?
13
+ param.delete # delete if transformed into nil
14
+
15
+ return
16
+ end
17
+
18
+ param.key = key unless key == param.key
19
+ param.value = value
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :fn
25
+ end
26
+ end
27
+ end
@@ -6,8 +6,11 @@ module TypedParams
6
6
  def initialize(options) = @options = options
7
7
  def call(value) = raise NotImplementedError
8
8
 
9
+ # wraps a callable e.g. Proc for use as a Validation
9
10
  def self.wrap(fn)
10
- -> v { raise ValidationError, 'is invalid' unless fn.call(v) }
11
+ return fn if fn in Validation
12
+
13
+ Wrapped.new(fn)
11
14
  end
12
15
 
13
16
  private
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typed_params/validations/validation'
4
+
5
+ module TypedParams
6
+ module Validations
7
+ class Wrapped < Validation
8
+ def initialize(fn) = @fn = fn
9
+
10
+ def call(value)
11
+ raise ValidationError, 'is invalid' unless fn.call(value)
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :fn
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypedParams
4
- VERSION = '1.4.1'
4
+ VERSION = '1.5.0'
5
5
  end
data/lib/typed_params.rb CHANGED
@@ -19,11 +19,13 @@ require 'typed_params/pipeline'
19
19
  require 'typed_params/processor'
20
20
  require 'typed_params/schema_set'
21
21
  require 'typed_params/schema'
22
+ require 'typed_params/transforms/collapse'
22
23
  require 'typed_params/transforms/key_alias'
23
24
  require 'typed_params/transforms/key_casing'
24
25
  require 'typed_params/transforms/nilify_blanks'
25
26
  require 'typed_params/transforms/noop'
26
27
  require 'typed_params/transforms/transform'
28
+ require 'typed_params/transforms/wrapped'
27
29
  require 'typed_params/transformer'
28
30
  require 'typed_params/types'
29
31
  require 'typed_params/types/any'
@@ -46,6 +48,7 @@ require 'typed_params/validations/format'
46
48
  require 'typed_params/validations/inclusion'
47
49
  require 'typed_params/validations/length'
48
50
  require 'typed_params/validations/validation'
51
+ require 'typed_params/validations/wrapped'
49
52
  require 'typed_params/validator'
50
53
 
51
54
  module TypedParams
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.1
4
+ version: 1.5.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-04 00:00:00.000000000 Z
11
+ date: 2026-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -73,11 +73,13 @@ files:
73
73
  - lib/typed_params/schema.rb
74
74
  - lib/typed_params/schema_set.rb
75
75
  - lib/typed_params/transformer.rb
76
+ - lib/typed_params/transforms/collapse.rb
76
77
  - lib/typed_params/transforms/key_alias.rb
77
78
  - lib/typed_params/transforms/key_casing.rb
78
79
  - lib/typed_params/transforms/nilify_blanks.rb
79
80
  - lib/typed_params/transforms/noop.rb
80
81
  - lib/typed_params/transforms/transform.rb
82
+ - lib/typed_params/transforms/wrapped.rb
81
83
  - lib/typed_params/types.rb
82
84
  - lib/typed_params/types/any.rb
83
85
  - lib/typed_params/types/array.rb
@@ -99,6 +101,7 @@ files:
99
101
  - lib/typed_params/validations/inclusion.rb
100
102
  - lib/typed_params/validations/length.rb
101
103
  - lib/typed_params/validations/validation.rb
104
+ - lib/typed_params/validations/wrapped.rb
102
105
  - lib/typed_params/validator.rb
103
106
  - lib/typed_params/version.rb
104
107
  homepage: https://github.com/keygen-sh/typed_params