typed_params 1.0.3 → 1.1.1

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: 440661d066c2dffa2f84361a952e7158ca7d56d16037ca2c937acb30f2f1cad6
4
- data.tar.gz: ad9f0aad3b817b5fa5f99d6d9eaa9e8f73d556c037cd03207bdc10b51fb26e81
3
+ metadata.gz: ab9149f0354b3110bc3a5610b06785055ad0e3ec00cc5e352b2daef7be902178
4
+ data.tar.gz: a48c899da8bc5d9fa9ee1041a2e35ff8f2f942b9ff083f7bf42e18e34e7813a2
5
5
  SHA512:
6
- metadata.gz: 5919d79cc5c6f4b8bc7c8b1f65520b04e7fb11632f3b20f50f1c99f9346602f2f471058d293da5e14709c6b5f95ae2f1b1ddf04fa3c235b1314ef701333b568d
7
- data.tar.gz: dea4d641834d9ae1184cdcc5653f1d04bdca18650d787d3557991ab03219ebc2234b36e9edaa9bd7733466852538491e3ad42e93029d9a748c7cea5a4b735105
6
+ metadata.gz: 4dabec975049677e23ab38f91c121f8f83a2dab36b753c9ad58f0693ad31ce780122b503b4b7554f6afe36568e3a85ef8c4d559591a8205bbadc9740e5cfd4b9
7
+ data.tar.gz: a4c8be23f0472002c9e317924091b624ac7cd640f08bfc515f61db119b72d20d6312919a4f45b31b14560d8bde08dad55467be058422acd4f39e225b927fd539
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.1
4
+
5
+ - Fix compatibility with Ruby 3.3.0 due to [#20091](https://bugs.ruby-lang.org/issues/20091).
6
+
7
+ ## 1.1.0
8
+
9
+ - Add memoization to `#typed_params` and `#typed_query` methods.
10
+
3
11
  ## 1.0.3
4
12
 
5
13
  - Revert 0b0aaa6b66edd3e4c3336e51fa340592e7ef9e86.
@@ -18,4 +26,4 @@
18
26
 
19
27
  ## 0.2.0
20
28
 
21
- - Test release.
29
+ - Test release.
data/README.md CHANGED
@@ -14,7 +14,7 @@ to serve millions of API requests per day.
14
14
  class UsersController < ApplicationController
15
15
  include TypedParams::Controller
16
16
 
17
- rescue_from TypedParams::InvalidParameterError, -> err {
17
+ rescue_from TypedParams::InvalidParameterError, with: -> err {
18
18
  render_bad_request err.message, source: err.path.to_s
19
19
  }
20
20
 
@@ -38,10 +38,16 @@ end
38
38
 
39
39
  Sponsored by:
40
40
 
41
- [![Keygen logo](https://github.com/keygen-sh/typed_params/assets/6979737/f2947915-2956-4415-a9c0-5411c388ea96)](https://keygen.sh)
41
+ <a href="https://keygen.sh?ref=typed_params">
42
+ <div>
43
+ <img src="https://keygen.sh/images/logo-pill.png" width="200" alt="Keygen">
44
+ </div>
45
+ </a>
42
46
 
43
47
  _An open, source-available software licensing and distribution API._
44
48
 
49
+ __
50
+
45
51
  Links:
46
52
 
47
53
  - [Installing `typed_params`](#installation)
@@ -52,6 +58,7 @@ Links:
52
58
  - [Query schemas](#query-schemas)
53
59
  - [Defining schemas](#defining-schemas)
54
60
  - [Shared schemas](#shared-schemas)
61
+ - [Namespaced schemas](#namespaced-schemas)
55
62
  - [Configuration](#configuration)
56
63
  - [Invalid parameters](#invalid-parameters)
57
64
  - [Unpermitted parameters](#unpermitted-parameters)
@@ -103,6 +110,7 @@ _We're working on improving the docs._
103
110
  - Reuse schemas across controllers by defining named schemas.
104
111
  - Run validations on params, similar to active model validations.
105
112
  - Run transforms on params before they hit your controller.
113
+ - Support formatters such as JSON:API.
106
114
 
107
115
  ## Usage
108
116
 
@@ -115,7 +123,7 @@ To start, include the controller module:
115
123
  class ApplicationController < ActionController::API
116
124
  include TypedParams::Controller
117
125
 
118
- rescue_from TypedParams::InvalidParameterError, -> err {
126
+ rescue_from TypedParams::InvalidParameterError, with: -> err {
119
127
  render_bad_request err.message, source: err.path.to_s
120
128
  }
121
129
  end
@@ -266,13 +274,28 @@ class PostsController < ApplicationController
266
274
  end
267
275
  ```
268
276
 
269
- Named schemas can have an optional `:namespace` as well.
277
+ ### Namespaced schemas
278
+
279
+ Schemas can have an optional `:namespace`. This can be especially useful when
280
+ defining and sharing schemas across multiple versions of an API.
270
281
 
271
282
  ```ruby
272
- typed_schema :post, namespace: :v1 do
273
- param :title, type: :string, length: { within: 10..80 }
274
- param :content, type: :string, length: { minimum: 100 }
275
- param :author_id, type: :integer
283
+ class PostsController < ApplicationController
284
+ typed_schema :post, namespace: :v1 do
285
+ param :title, type: :string, length: { within: 10..80 }
286
+ param :content, type: :string, length: { minimum: 100 }
287
+ param :author_id, type: :integer
288
+ end
289
+
290
+ typed_params schema: %i[v1 post]
291
+ def create
292
+ # ...
293
+ end
294
+
295
+ typed_params schema: %i[v1 post]
296
+ def update
297
+ # ...
298
+ end
276
299
  end
277
300
  ```
278
301
 
@@ -345,7 +368,7 @@ TypedParams.configure do |config|
345
368
  #
346
369
  # With an invalid `child_key`, the path would be:
347
370
  #
348
- # rescue_from TypedParams::UnpermittedParameterError, -> err {
371
+ # rescue_from TypedParams::UnpermittedParameterError, with: -> err {
349
372
  # puts err.path.to_s # => parentKey.childKey
350
373
  # }
351
374
  #
@@ -362,7 +385,7 @@ You can rescue this error at the controller-level like so:
362
385
 
363
386
  ```ruby
364
387
  class ApplicationController < ActionController::API
365
- rescue_from TypedParams::InvalidParameterError, -> err {
388
+ rescue_from TypedParams::InvalidParameterError, with: -> err {
366
389
  render_bad_request "invalid parameter: #{err.message}", parameter: err.path.to_dot_notation
367
390
  }
368
391
  end
@@ -388,7 +411,7 @@ You can rescue this error at the controller-level like so:
388
411
  ```ruby
389
412
  class ApplicationController < ActionController::API
390
413
  # NOTE: Should be rescued before TypedParams::InvalidParameterError
391
- rescue_from TypedParams::UnpermittedParameterError, -> err {
414
+ rescue_from TypedParams::UnpermittedParameterError, with: -> err {
392
415
  render_bad_request "unpermitted parameter: #{err.path.to_jsonapi_pointer}"
393
416
  }
394
417
  end
@@ -621,9 +644,18 @@ The lambda must return a tuple with the new key and value.
621
644
  #### Validate parameter
622
645
 
623
646
  Define a custom validation for the parameter, outside of the default
624
- validations.
647
+ validations. The can be useful for defining mutually exclusive params,
648
+ or even validating that an ID exists before proceeding.
625
649
 
626
650
  ```ruby
651
+ # Mutually exclusive params (i.e. either-or, not both)
652
+ param :login, type: :hash, validate: -> v { v.key?(:username) ^ v.key?(:email) } do
653
+ param :username, type: :string, optional: true
654
+ param :email, type: :string, optional: true
655
+ param :password, type: :string
656
+ end
657
+
658
+ # Assert user exists
627
659
  param :user, type: :integer, validate: -> id {
628
660
  User.exists?(id)
629
661
  }
@@ -633,6 +665,15 @@ The lambda should accept a value and return a boolean. When the boolean
633
665
  evaluates to `false`, a `TypedParams::InvalidParameterError` will
634
666
  be raised.
635
667
 
668
+ To customize the error message, the lambda can raise a `TypedParams::ValidationError`
669
+ error:
670
+
671
+ ```ruby
672
+ param :invalid, type: :string, validate: -> v {
673
+ raise TypedParams::ValidationError, 'is always invalid'
674
+ }
675
+ ```
676
+
636
677
  ### Shared options
637
678
 
638
679
  You can define a set of options that will be applied to immediate
@@ -708,7 +749,7 @@ which may be a nested schema.
708
749
  ```ruby
709
750
  # array of hashes
710
751
  param :boundless_array, type: :array do
711
- item type: :hash do
752
+ items type: :hash do
712
753
  # ...
713
754
  end
714
755
  end
@@ -3,15 +3,19 @@
3
3
  require 'typed_params/handler'
4
4
  require 'typed_params/handler_set'
5
5
  require 'typed_params/schema_set'
6
+ require 'typed_params/memoize'
6
7
 
7
8
  module TypedParams
8
9
  module Controller
9
10
  extend ActiveSupport::Concern
10
11
 
11
12
  included do
13
+ include Memoize
14
+
12
15
  cattr_accessor :typed_handlers, default: HandlerSet.new
13
16
  cattr_accessor :typed_schemas, default: SchemaSet.new
14
17
 
18
+ memoize
15
19
  def typed_params(format: AUTO)
16
20
  handler = typed_handlers.params[self.class, action_name.to_sym]
17
21
 
@@ -42,6 +46,7 @@ module TypedParams
42
46
  )
43
47
  end
44
48
 
49
+ memoize
45
50
  def typed_query(format: AUTO)
46
51
  handler = typed_handlers.query[self.class, action_name.to_sym]
47
52
 
@@ -47,7 +47,7 @@ module TypedParams
47
47
  # │ 1 ││ 2 │ │ 5 │
48
48
  # └───┘└───┘ └───┘
49
49
  #
50
- def depth_first_map(param, &)
50
+ def depth_first_map(param, &block)
51
51
  return if param.nil?
52
52
 
53
53
  # Postorder DFS, so we'll visit the children first.
@@ -55,12 +55,12 @@ module TypedParams
55
55
  case param.schema.children
56
56
  in Array if param.array?
57
57
  if param.schema.indexed?
58
- param.schema.children.each_with_index { |v, i| self.class.call(param[i], schema: v, controller:, &) }
58
+ param.schema.children.each_with_index { |v, i| self.class.call(param[i], schema: v, controller:, &block) }
59
59
  else
60
- param.value.each { |v| self.class.call(v, schema: param.schema.children.first, controller:, &) }
60
+ param.value.each { |v| self.class.call(v, schema: param.schema.children.first, controller:, &block) }
61
61
  end
62
62
  in Hash if param.hash?
63
- param.schema.children.each { |k, v| self.class.call(param[k], schema: v, controller:, &) }
63
+ param.schema.children.each { |k, v| self.class.call(param[k], schema: v, controller:, &block) }
64
64
  else
65
65
  end
66
66
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedParams
4
+ module Memoize
5
+ def self.included(klass) = klass.extend ClassMethods
6
+
7
+ module ClassMethods
8
+ cattr_accessor :memoize_queue, default: []
9
+
10
+ def memoize
11
+ if memoize_queue.include?(self)
12
+ memoize_queue.clear
13
+
14
+ raise RuntimeError, 'memoize cannot be called more than once in succession'
15
+ end
16
+
17
+ memoize_queue << self
18
+ end
19
+
20
+ private
21
+
22
+ def singleton_method_added(method_name)
23
+ while klass = memoize_queue.shift
24
+ raise RuntimeError, "memoize cannot be instrumented more than once for class method #{method_name}" if
25
+ klass.respond_to?(:"unmemoized_#{method_name}")
26
+
27
+ method_visibility = case
28
+ when klass.singleton_class.private_method_defined?(method_name)
29
+ :private
30
+ when klass.singleton_class.protected_method_defined?(method_name)
31
+ :protected
32
+ when klass.singleton_class.public_method_defined?(method_name)
33
+ :public
34
+ end
35
+
36
+ klass.singleton_class.send(:alias_method, :"unmemoized_#{method_name}", method_name)
37
+ klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
38
+ def self.#{method_name}(*args, **kwargs, &block)
39
+ key = args.hash ^ kwargs.hash ^ block.hash
40
+
41
+ return @_memoized_#{method_name}_values[key] if
42
+ defined?(@_memoized_#{method_name}_values) && @_memoized_#{method_name}_values.key?(key)
43
+
44
+ value = unmemoized_#{method_name}(*args, **kwargs, &block)
45
+ memo = @_memoized_#{method_name}_values ||= {}
46
+ memo[key] = value
47
+
48
+ value
49
+ end
50
+ RUBY
51
+
52
+ klass.singleton_class.send(method_visibility, method_name)
53
+ end
54
+
55
+ super
56
+ ensure
57
+ memoize_queue.clear
58
+ end
59
+
60
+ def method_added(method_name)
61
+ while klass = memoize_queue.shift
62
+ raise RuntimeError, "memoize cannot be instrumented more than once for instance method #{method_name}" if
63
+ klass.method_defined?(:"unmemoized_#{method_name}")
64
+
65
+ method_visibility = case
66
+ when klass.private_method_defined?(method_name)
67
+ :private
68
+ when klass.protected_method_defined?(method_name)
69
+ :protected
70
+ when klass.public_method_defined?(method_name)
71
+ :public
72
+ end
73
+
74
+ klass.alias_method :"unmemoized_#{method_name}", method_name
75
+ klass.class_eval <<~RUBY, __FILE__, __LINE__ + 1
76
+ def #{method_name}(*args, **kwargs, &block)
77
+ key = args.hash ^ kwargs.hash ^ block.hash
78
+
79
+ return @_memoized_#{method_name}_values[key] if
80
+ defined?(@_memoized_#{method_name}_values) && @_memoized_#{method_name}_values.key?(key)
81
+
82
+ value = unmemoized_#{method_name}(*args, **kwargs, &block)
83
+ memo = @_memoized_#{method_name}_values ||= {}
84
+ memo[key] = value
85
+
86
+ value
87
+ end
88
+ RUBY
89
+
90
+ klass.send(method_visibility, method_name)
91
+ end
92
+
93
+ super
94
+ ensure
95
+ memoize_queue.clear
96
+ end
97
+ end
98
+ end
99
+ end
@@ -211,7 +211,7 @@ module TypedParams
211
211
 
212
212
  ##
213
213
  # params defines multiple like-parameters for a hash schema.
214
- def params(*keys, **kwargs, &) = keys.each { param(_1, **kwargs, &) }
214
+ def params(*keys, **kwargs, &block) = keys.each { param(_1, **kwargs, &block) }
215
215
 
216
216
  ##
217
217
  # item defines an indexed parameter for an array schema.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypedParams
4
- VERSION = '1.0.3'
4
+ VERSION = '1.1.1'
5
5
  end
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.0.3
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zeke Gabrielse
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-08 00:00:00.000000000 Z
11
+ date: 2024-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -63,6 +63,7 @@ files:
63
63
  - lib/typed_params/handler.rb
64
64
  - lib/typed_params/handler_set.rb
65
65
  - lib/typed_params/mapper.rb
66
+ - lib/typed_params/memoize.rb
66
67
  - lib/typed_params/namespaced_set.rb
67
68
  - lib/typed_params/parameter.rb
68
69
  - lib/typed_params/parameterizer.rb