typed_params 1.0.3 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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