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 +4 -4
- data/CHANGELOG.md +9 -1
- data/README.md +54 -13
- data/lib/typed_params/controller.rb +5 -0
- data/lib/typed_params/mapper.rb +4 -4
- data/lib/typed_params/memoize.rb +99 -0
- data/lib/typed_params/schema.rb +1 -1
- data/lib/typed_params/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab9149f0354b3110bc3a5610b06785055ad0e3ec00cc5e352b2daef7be902178
|
4
|
+
data.tar.gz: a48c899da8bc5d9fa9ee1041a2e35ff8f2f942b9ff083f7bf42e18e34e7813a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
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
|
-
|
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
|
|
data/lib/typed_params/mapper.rb
CHANGED
@@ -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
|
data/lib/typed_params/schema.rb
CHANGED
@@ -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.
|
data/lib/typed_params/version.rb
CHANGED
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.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:
|
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
|