typed_params 1.0.3 → 1.1.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/README.md +53 -13
- data/lib/typed_params/controller.rb +5 -0
- data/lib/typed_params/memoize.rb +99 -0
- 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: 532f36036d10c99438c7764f96edee2a81609957e91549be1d39c09eeac3b986
|
4
|
+
data.tar.gz: 2780ca74ef0c99371dc87486134e37b25c8de93a4814f297d7d1955fab18ba32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52d65870463fa00390b94bb2b469f3c12dda164518d4a32ed3151c5f09303ceb546ab4fcc76388eb0c42902a19a64ba249eb44119492ab771175c876821e0d7e
|
7
|
+
data.tar.gz: cd70318ed475aa00a8cbdafc89656957a93abe17c1108b0d39bca744102a1e91546d483168b8e5a9e8ac1ae9b58bb17870594d8611b8a05d46d46862f744d7ae
|
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)
|
@@ -115,7 +122,7 @@ To start, include the controller module:
|
|
115
122
|
class ApplicationController < ActionController::API
|
116
123
|
include TypedParams::Controller
|
117
124
|
|
118
|
-
rescue_from TypedParams::InvalidParameterError, -> err {
|
125
|
+
rescue_from TypedParams::InvalidParameterError, with: -> err {
|
119
126
|
render_bad_request err.message, source: err.path.to_s
|
120
127
|
}
|
121
128
|
end
|
@@ -266,13 +273,28 @@ class PostsController < ApplicationController
|
|
266
273
|
end
|
267
274
|
```
|
268
275
|
|
269
|
-
|
276
|
+
### Namespaced schemas
|
277
|
+
|
278
|
+
Schemas can have an optional `:namespace`. This can be especially useful when
|
279
|
+
defining and sharing schemas across multiple versions of an API.
|
270
280
|
|
271
281
|
```ruby
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
282
|
+
class PostsController < ApplicationController
|
283
|
+
typed_schema :post, namespace: :v1 do
|
284
|
+
param :title, type: :string, length: { within: 10..80 }
|
285
|
+
param :content, type: :string, length: { minimum: 100 }
|
286
|
+
param :author_id, type: :integer
|
287
|
+
end
|
288
|
+
|
289
|
+
typed_params schema: %i[v1 post]
|
290
|
+
def create
|
291
|
+
# ...
|
292
|
+
end
|
293
|
+
|
294
|
+
typed_params schema: %i[v1 post]
|
295
|
+
def update
|
296
|
+
# ...
|
297
|
+
end
|
276
298
|
end
|
277
299
|
```
|
278
300
|
|
@@ -345,7 +367,7 @@ TypedParams.configure do |config|
|
|
345
367
|
#
|
346
368
|
# With an invalid `child_key`, the path would be:
|
347
369
|
#
|
348
|
-
# rescue_from TypedParams::UnpermittedParameterError, -> err {
|
370
|
+
# rescue_from TypedParams::UnpermittedParameterError, with: -> err {
|
349
371
|
# puts err.path.to_s # => parentKey.childKey
|
350
372
|
# }
|
351
373
|
#
|
@@ -362,7 +384,7 @@ You can rescue this error at the controller-level like so:
|
|
362
384
|
|
363
385
|
```ruby
|
364
386
|
class ApplicationController < ActionController::API
|
365
|
-
rescue_from TypedParams::InvalidParameterError, -> err {
|
387
|
+
rescue_from TypedParams::InvalidParameterError, with: -> err {
|
366
388
|
render_bad_request "invalid parameter: #{err.message}", parameter: err.path.to_dot_notation
|
367
389
|
}
|
368
390
|
end
|
@@ -388,7 +410,7 @@ You can rescue this error at the controller-level like so:
|
|
388
410
|
```ruby
|
389
411
|
class ApplicationController < ActionController::API
|
390
412
|
# NOTE: Should be rescued before TypedParams::InvalidParameterError
|
391
|
-
rescue_from TypedParams::UnpermittedParameterError, -> err {
|
413
|
+
rescue_from TypedParams::UnpermittedParameterError, with: -> err {
|
392
414
|
render_bad_request "unpermitted parameter: #{err.path.to_jsonapi_pointer}"
|
393
415
|
}
|
394
416
|
end
|
@@ -621,9 +643,18 @@ The lambda must return a tuple with the new key and value.
|
|
621
643
|
#### Validate parameter
|
622
644
|
|
623
645
|
Define a custom validation for the parameter, outside of the default
|
624
|
-
validations.
|
646
|
+
validations. The can be useful for defining mutually exclusive params,
|
647
|
+
or even validating that an ID exists before proceeding.
|
625
648
|
|
626
649
|
```ruby
|
650
|
+
# Mutually exclusive params (i.e. either-or, not both)
|
651
|
+
param :login, type: :hash, validate: -> v { v.key?(:username) ^ v.key?(:email) } do
|
652
|
+
param :username, type: :string, optional: true
|
653
|
+
param :email, type: :string, optional: true
|
654
|
+
param :password, type: :string
|
655
|
+
end
|
656
|
+
|
657
|
+
# Assert user exists
|
627
658
|
param :user, type: :integer, validate: -> id {
|
628
659
|
User.exists?(id)
|
629
660
|
}
|
@@ -633,6 +664,15 @@ The lambda should accept a value and return a boolean. When the boolean
|
|
633
664
|
evaluates to `false`, a `TypedParams::InvalidParameterError` will
|
634
665
|
be raised.
|
635
666
|
|
667
|
+
To customize the error message, the lambda can raise a `TypedParams::ValidationError`
|
668
|
+
error:
|
669
|
+
|
670
|
+
```ruby
|
671
|
+
param :invalid, type: :string, validate: -> v {
|
672
|
+
raise TypedParams::ValidationError, 'is always invalid'
|
673
|
+
}
|
674
|
+
```
|
675
|
+
|
636
676
|
### Shared options
|
637
677
|
|
638
678
|
You can define a set of options that will be applied to immediate
|
@@ -708,7 +748,7 @@ which may be a nested schema.
|
|
708
748
|
```ruby
|
709
749
|
# array of hashes
|
710
750
|
param :boundless_array, type: :array do
|
711
|
-
|
751
|
+
items type: :hash do
|
712
752
|
# ...
|
713
753
|
end
|
714
754
|
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
|
|
@@ -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/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.0
|
4
|
+
version: 1.1.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: 2023-
|
11
|
+
date: 2023-08-17 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
|