typed_params 1.0.0 → 1.0.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 +4 -0
- data/README.md +187 -47
- data/lib/typed_params/controller.rb +2 -4
- data/lib/typed_params/parameterizer.rb +0 -6
- data/lib/typed_params/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26b2e5feb7cf9a5dee31da13a451af45b0995fd586593d5ad0aff3619dbae7fb
|
4
|
+
data.tar.gz: b0c56e75dfa90e7252d088a848155631bd943707c2d64628d366275c040b319f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ae0982eefae7e30a6f1d11bc3bf9494c9b94d752f3eb9b130d614a32c19c1b8795c3d2a25c34e50d66171d3a2aa84fbaa4137e6f78454d87da25dfbf30dd478
|
7
|
+
data.tar.gz: 6abda1500599b0d982b3808108efc1e9fff777316c5d158bcfe4aadad88612df9ccdc3e63cd2991ecb1c64f72145ae08fd60c9be262d774f14dac29a974a9c86
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -10,6 +10,32 @@ parameter schemas for Rails APIs.
|
|
10
10
|
This gem was extracted from [Keygen](https://keygen.sh) and is being used in production
|
11
11
|
to serve millions of API requests per day.
|
12
12
|
|
13
|
+
```ruby
|
14
|
+
class UsersController < ApplicationController
|
15
|
+
include TypedParams::Controller
|
16
|
+
|
17
|
+
rescue_from TypedParams::InvalidParameterError, -> err {
|
18
|
+
render_bad_request err.message, source: err.path.to_s
|
19
|
+
}
|
20
|
+
|
21
|
+
typed_params {
|
22
|
+
param :first_name, type: :string, optional: true
|
23
|
+
param :last_name, type: :string, optional: true
|
24
|
+
param :email, type: :string
|
25
|
+
param :password, type: :string
|
26
|
+
}
|
27
|
+
def create
|
28
|
+
user = User.new(user_params)
|
29
|
+
|
30
|
+
if user.save
|
31
|
+
render_created user, location: v1_user_url(user)
|
32
|
+
else
|
33
|
+
render_unprocessable_resource user
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
13
39
|
Sponsored by:
|
14
40
|
|
15
41
|
[](https://keygen.sh)
|
@@ -27,13 +53,14 @@ Links:
|
|
27
53
|
- [Defining schemas](#defining-schemas)
|
28
54
|
- [Shared schemas](#shared-schemas)
|
29
55
|
- [Configuration](#configuration)
|
30
|
-
- [Unpermitted parameters](#unpermitted-parameters)
|
31
56
|
- [Invalid parameters](#invalid-parameters)
|
57
|
+
- [Unpermitted parameters](#unpermitted-parameters)
|
32
58
|
- [Parameter options](#parameter-options)
|
33
59
|
- [Shared options](#shared-options)
|
34
60
|
- [Scalar types](#scalar-types)
|
35
61
|
- [Non-scalar types](#non-scalar-types)
|
36
62
|
- [Custom types](#custom-types)
|
63
|
+
- [Formats](#formats)
|
37
64
|
- [Contributing](#contributing)
|
38
65
|
- [License](#license)
|
39
66
|
|
@@ -87,6 +114,10 @@ To start, include the controller module:
|
|
87
114
|
```ruby
|
88
115
|
class ApplicationController < ActionController::API
|
89
116
|
include TypedParams::Controller
|
117
|
+
|
118
|
+
rescue_from TypedParams::InvalidParameterError, -> err {
|
119
|
+
render_bad_request err.message, source: err.path.to_s
|
120
|
+
}
|
90
121
|
end
|
91
122
|
```
|
92
123
|
|
@@ -202,7 +233,7 @@ end
|
|
202
233
|
|
203
234
|
By default, all root schemas are a [`:hash`](#hash-type) schema. This is because both
|
204
235
|
`request.request_parameters` and `request.query_parameters` are hashes. Eventually,
|
205
|
-
we'd like [to make that configurable](https://github.com/keygen-sh/typed_params/blob/67e9a34ce62c9cddbd2bd313e4e9f096f8744b83/lib/
|
236
|
+
we'd like [to make that configurable](https://github.com/keygen-sh/typed_params/blob/67e9a34ce62c9cddbd2bd313e4e9f096f8744b83/lib/typed_parameters/controller.rb#L24-L27),
|
206
237
|
so that you could use a top-level array schema. You can create nested schemas via
|
207
238
|
the [`:hash`](#hash-type) and [`:array`](#array-type) types.
|
208
239
|
|
@@ -235,6 +266,16 @@ class PostsController < ApplicationController
|
|
235
266
|
end
|
236
267
|
```
|
237
268
|
|
269
|
+
Named schemas can have an optional `:namespace` as well.
|
270
|
+
|
271
|
+
```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
|
276
|
+
end
|
277
|
+
```
|
278
|
+
|
238
279
|
### Configuration
|
239
280
|
|
240
281
|
```ruby
|
@@ -304,7 +345,7 @@ TypedParams.configure do |config|
|
|
304
345
|
#
|
305
346
|
# With an invalid `child_key`, the path would be:
|
306
347
|
#
|
307
|
-
# rescue_from TypedParams::UnpermittedParameterError, err
|
348
|
+
# rescue_from TypedParams::UnpermittedParameterError, -> err {
|
308
349
|
# puts err.path.to_s # => parentKey.childKey
|
309
350
|
# }
|
310
351
|
#
|
@@ -312,50 +353,55 @@ TypedParams.configure do |config|
|
|
312
353
|
end
|
313
354
|
```
|
314
355
|
|
315
|
-
###
|
316
|
-
|
317
|
-
By default, `.typed_params` is [`:strict`](#strict-parameter). This means that if any unpermitted parameters
|
318
|
-
are provided, a `TypedParams::UnpermittedParameterError` will be raised.
|
356
|
+
### Invalid parameters
|
319
357
|
|
320
|
-
|
321
|
-
will be
|
358
|
+
When a parameter is provided, but it fails validation (e.g. a type mismatch), a
|
359
|
+
`TypedParams::InvalidParameterError` error will be raised.
|
322
360
|
|
323
|
-
You can rescue this error at the
|
361
|
+
You can rescue this error at the controller-level like so:
|
324
362
|
|
325
363
|
```ruby
|
326
364
|
class ApplicationController < ActionController::API
|
327
|
-
rescue_from TypedParams::
|
328
|
-
render_bad_request "
|
365
|
+
rescue_from TypedParams::InvalidParameterError, -> err {
|
366
|
+
render_bad_request "invalid parameter: #{err.message}", parameter: err.path.to_dot_notation
|
329
367
|
}
|
330
368
|
end
|
331
369
|
```
|
332
370
|
|
333
|
-
The `TypedParams::
|
371
|
+
The `TypedParams::InvalidParameterError` error object has the following attributes:
|
334
372
|
|
335
|
-
- `#message` - the error message, e.g. `
|
336
|
-
- `#path` - a `Path` object with a pointer to the
|
337
|
-
- `#source` - either `:params` or `:query`, depending on where the
|
373
|
+
- `#message` - the error message, e.g. `type mismatch (received string expected integer)`.
|
374
|
+
- `#path` - a `Path` object with a pointer to the invalid parameter.
|
375
|
+
- `#source` - either `:params` or `:query`, depending on where the invalid parameter came
|
376
|
+
from (i.e. request body vs query parameters, respectively).
|
338
377
|
|
339
|
-
###
|
378
|
+
### Unpermitted parameters
|
340
379
|
|
341
|
-
|
342
|
-
`TypedParams::
|
380
|
+
By default, `.typed_params` is [`:strict`](#strict-parameter). This means that if any unpermitted parameters
|
381
|
+
are provided, a `TypedParams::UnpermittedParameterError` will be raised.
|
343
382
|
|
344
|
-
|
383
|
+
For `.typed_query`, the default is non-strict. This means that any unpermitted parameters
|
384
|
+
will be ignored.
|
385
|
+
|
386
|
+
You can rescue this error at the controller-level like so:
|
345
387
|
|
346
388
|
```ruby
|
347
389
|
class ApplicationController < ActionController::API
|
348
|
-
|
349
|
-
|
390
|
+
# NOTE: Should be rescued before TypedParams::InvalidParameterError
|
391
|
+
rescue_from TypedParams::UnpermittedParameterError, -> err {
|
392
|
+
render_bad_request "unpermitted parameter: #{err.path.to_jsonapi_pointer}"
|
350
393
|
}
|
351
394
|
end
|
352
395
|
```
|
353
396
|
|
354
|
-
The `TypedParams::
|
397
|
+
The `TypedParams::UnpermittedParameterError` error object has the following attributes:
|
355
398
|
|
356
|
-
- `#message` - the error message, e.g. `
|
357
|
-
- `#path` - a `Path` object with a pointer to the
|
358
|
-
- `#source` - either `:params` or `:query`, depending on where the
|
399
|
+
- `#message` - the error message, e.g. `unpermitted parameter`.
|
400
|
+
- `#path` - a `Path` object with a pointer to the unpermitted parameter.
|
401
|
+
- `#source` - either `:params` or `:query`, depending on where the unpermitted parameter came
|
402
|
+
from (i.e. request body vs query parameters, respectively).
|
403
|
+
|
404
|
+
It inherits from [`TypedParams::InvalidParameterError`](#invalid-parameters).
|
359
405
|
|
360
406
|
### Parameter options
|
361
407
|
|
@@ -366,9 +412,9 @@ Parameters can have validations, transforms, and more.
|
|
366
412
|
- [`:strict`](#strict-parameter)
|
367
413
|
- [`:optional`](#optional-parameter)
|
368
414
|
- [`:if` and `:unless`](#conditional-parameter)
|
369
|
-
- [`:as`](#
|
415
|
+
- [`:as`](#rename-parameter)
|
370
416
|
- [`:noop`](#noop-parameter)
|
371
|
-
- [`:coerce`](#
|
417
|
+
- [`:coerce`](#coerce-parameter)
|
372
418
|
- [`:allow_blank`](#allow-blank)
|
373
419
|
- [`:allow_nil`](#allow-nil)
|
374
420
|
- [`:allow_non_scalars`](#allow-non-scalars)
|
@@ -438,14 +484,14 @@ param :role, type: :string, unless: :guest?
|
|
438
484
|
|
439
485
|
The lambda will be evaled within the current controller context.
|
440
486
|
|
441
|
-
####
|
487
|
+
#### Rename parameter
|
442
488
|
|
443
489
|
Apply a transformation that renames the parameter.
|
444
490
|
|
445
491
|
```ruby
|
446
492
|
param :user, type: :integer, as: :user_id
|
447
493
|
|
448
|
-
typed_params # => { user_id:
|
494
|
+
typed_params # => { user_id: 1 }
|
449
495
|
```
|
450
496
|
|
451
497
|
In this example, the parameter would be accepted as `:user`, but renamed
|
@@ -461,7 +507,7 @@ param :foo, type: :string, noop: true
|
|
461
507
|
|
462
508
|
By default, this is `false`.
|
463
509
|
|
464
|
-
####
|
510
|
+
#### Coerce parameter
|
465
511
|
|
466
512
|
The parameter will be coerced if its type is coercible and the parameter has a
|
467
513
|
type mismatch. The coercion can fail, e.g. `:integer` to `:hash`, and if it does,
|
@@ -522,7 +568,7 @@ By default, this is disabled.
|
|
522
568
|
The parameter must be included in the array or range.
|
523
569
|
|
524
570
|
```ruby
|
525
|
-
param :log_level, type: :string,
|
571
|
+
param :log_level, type: :string, inclusion: { in: %w[DEBUG INFO WARN ERROR FATAL] }
|
526
572
|
param :priority, type: :integer, inclusion: { in: 0..9 }
|
527
573
|
```
|
528
574
|
|
@@ -531,7 +577,7 @@ param :priority, type: :integer, inclusion: { in: 0..9 }
|
|
531
577
|
The parameter must be excluded from the array or range.
|
532
578
|
|
533
579
|
```ruby
|
534
|
-
param :custom_log_level, type: :string,
|
580
|
+
param :custom_log_level, type: :string, exclusion: { in: %w[DEBUG INFO WARN ERROR FATAL] }
|
535
581
|
param :custom_priority, type: :integer, exclusion: { in: 0..9 }
|
536
582
|
```
|
537
583
|
|
@@ -562,15 +608,15 @@ Transform the parameter using a lambda. This is commonly used to transform a
|
|
562
608
|
parameter into a nested attributes hash or array.
|
563
609
|
|
564
610
|
```ruby
|
565
|
-
param :
|
566
|
-
[:
|
611
|
+
param :role, type: :string, transform: -> _, name {
|
612
|
+
[:role_attributes, { name: }]
|
567
613
|
}
|
568
614
|
```
|
569
615
|
|
570
616
|
The lambda must accept a key (the current parameter key), and a value (the
|
571
617
|
current parameter value).
|
572
618
|
|
573
|
-
The
|
619
|
+
The lambda must return a tuple with the new key and value.
|
574
620
|
|
575
621
|
#### Validate parameter
|
576
622
|
|
@@ -613,39 +659,39 @@ end
|
|
613
659
|
|
614
660
|
#### String type
|
615
661
|
|
616
|
-
Defines a string parameter. Must be a `String`.
|
662
|
+
Type `:string`. Defines a string parameter. Must be a `String`.
|
617
663
|
|
618
664
|
#### Boolean type
|
619
665
|
|
620
|
-
Defines a boolean parameter. Must be `TrueClass` or `FalseClass`.
|
666
|
+
Type `:boolean`. Defines a boolean parameter. Must be `TrueClass` or `FalseClass`.
|
621
667
|
|
622
668
|
#### Integer type
|
623
669
|
|
624
|
-
Defines an integer parameter. Must be an `Integer`.
|
670
|
+
Type `:integer`. Defines an integer parameter. Must be an `Integer`.
|
625
671
|
|
626
672
|
#### Float type
|
627
673
|
|
628
|
-
Defines a float parameter. Must be a `Float`.
|
674
|
+
Type `:float`. Defines a float parameter. Must be a `Float`.
|
629
675
|
|
630
676
|
#### Decimal type
|
631
677
|
|
632
|
-
Defines a decimal parameter. Must be a `BigDecimal`.
|
678
|
+
Type `:decimal`. Defines a decimal parameter. Must be a `BigDecimal`.
|
633
679
|
|
634
680
|
#### Number type
|
635
681
|
|
636
|
-
Defines a number parameter. Must be either an `Integer`, a `Float`, or a `BigDecimal`.
|
682
|
+
Type `:number`. Defines a number parameter. Must be either an `Integer`, a `Float`, or a `BigDecimal`.
|
637
683
|
|
638
684
|
#### Symbol type
|
639
685
|
|
640
|
-
Defines a symbol parameter. Must be a `Symbol`.
|
686
|
+
Type `:symbol`. Defines a symbol parameter. Must be a `Symbol`.
|
641
687
|
|
642
688
|
#### Time type
|
643
689
|
|
644
|
-
Defines a time parameter. Must be a `Time`.
|
690
|
+
Type `:time`. Defines a time parameter. Must be a `Time`.
|
645
691
|
|
646
692
|
#### Date type
|
647
693
|
|
648
|
-
Defines a
|
694
|
+
Type `:date`. Defines a date parameter. Must be a `Date`.
|
649
695
|
|
650
696
|
### Non-scalar types
|
651
697
|
|
@@ -654,7 +700,7 @@ Defines a time parameter. Must be a `Date`.
|
|
654
700
|
|
655
701
|
#### Array type
|
656
702
|
|
657
|
-
Defines an array parameter. Must be an `Array`.
|
703
|
+
Type `:array`. Defines an array parameter. Must be an `Array`.
|
658
704
|
|
659
705
|
Arrays are a special type. They can accept a block that defines its item types,
|
660
706
|
which may be a nested schema.
|
@@ -675,7 +721,7 @@ end
|
|
675
721
|
|
676
722
|
#### Hash type
|
677
723
|
|
678
|
-
Defines a hash parameter. Must be a `Hash`.
|
724
|
+
Type `:hash`. Defines a hash parameter. Must be a `Hash`.
|
679
725
|
|
680
726
|
Hashes are a special type. They can accept a block that defines a nested schema.
|
681
727
|
|
@@ -721,6 +767,100 @@ TypedParams.types.register(:metadata,
|
|
721
767
|
)
|
722
768
|
```
|
723
769
|
|
770
|
+
### Formats
|
771
|
+
|
772
|
+
Out of the box, `typed_params` ships with two formatters. Formatters are
|
773
|
+
run after all validations and transforms, formatting the params from
|
774
|
+
one format to another format.
|
775
|
+
|
776
|
+
By default, no formatter is used.
|
777
|
+
|
778
|
+
#### JSONAPI format
|
779
|
+
|
780
|
+
You can add convenient support for JSONAPI by using the `:jsonapi` format.
|
781
|
+
|
782
|
+
All request `data` will be transformed into a hash, useable within models.
|
783
|
+
|
784
|
+
In addition, request `meta` will be available inside of the controller
|
785
|
+
action with the following methods:
|
786
|
+
|
787
|
+
- `#{controller_name.singularize}_meta`
|
788
|
+
- `#typed_meta`
|
789
|
+
|
790
|
+
```ruby
|
791
|
+
class UsersController < ApplicationController
|
792
|
+
typed_params {
|
793
|
+
format :jsonapi
|
794
|
+
|
795
|
+
param :data, type: :hash do
|
796
|
+
param :type, type: :string, inclusion: { in: %w[users user] }, noop: true
|
797
|
+
param :id, type: :string, noop: true
|
798
|
+
param :attributes, type: :hash do
|
799
|
+
param :first_name, type: :string, optional: true
|
800
|
+
param :last_name, type: :string, optional: true
|
801
|
+
param :email, type: :string, format: { with: /@/ }
|
802
|
+
param :password, type: :string
|
803
|
+
end
|
804
|
+
param :relationships, type: :hash do
|
805
|
+
param :team, type: :hash do
|
806
|
+
param :data, type: :hash do
|
807
|
+
param :type, type: :string, inclusion: { in: %w[teams team] }
|
808
|
+
param :id, type: :string
|
809
|
+
end
|
810
|
+
end
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
param :meta, type: :hash, optional: true do
|
815
|
+
param :affilate_id, type: :string, optional: true
|
816
|
+
end
|
817
|
+
}
|
818
|
+
def create
|
819
|
+
puts user_params
|
820
|
+
# => {
|
821
|
+
# first_name: 'John',
|
822
|
+
# last_name: 'Smith',
|
823
|
+
# email: 'json@smith.example',
|
824
|
+
# password: '7c84241a1102',
|
825
|
+
# team_id: '1',
|
826
|
+
# }
|
827
|
+
|
828
|
+
puts user_meta
|
829
|
+
# => { affilate_id: 'e805' }
|
830
|
+
end
|
831
|
+
end
|
832
|
+
```
|
833
|
+
|
834
|
+
#### Rails format
|
835
|
+
|
836
|
+
You can add conventional wrapped params using the `:rails` format.
|
837
|
+
|
838
|
+
```ruby
|
839
|
+
class UsersController < ApplicationController
|
840
|
+
typed_params {
|
841
|
+
format :rails
|
842
|
+
|
843
|
+
param :first_name, type: :string, optional: true
|
844
|
+
param :last_name, type: :string, optional: true
|
845
|
+
param :email, type: :string, format: { with: /@/ }
|
846
|
+
param :password, type: :string
|
847
|
+
param :team_id, type: :string
|
848
|
+
}
|
849
|
+
def create
|
850
|
+
puts user_params
|
851
|
+
# => {
|
852
|
+
# user: {
|
853
|
+
# first_name: 'John',
|
854
|
+
# last_name: 'Smith',
|
855
|
+
# email: 'json@smith.example',
|
856
|
+
# password: '7c84241a1102',
|
857
|
+
# team_id: '1',
|
858
|
+
# }
|
859
|
+
# }
|
860
|
+
end
|
861
|
+
end
|
862
|
+
```
|
863
|
+
|
724
864
|
## Is it any good?
|
725
865
|
|
726
866
|
[Yes.](https://news.ycombinator.com/item?id=3067434)
|
@@ -71,8 +71,6 @@ module TypedParams
|
|
71
71
|
|
72
72
|
private
|
73
73
|
|
74
|
-
def typed_namespace = self.class
|
75
|
-
|
76
74
|
def respond_to_missing?(method_name, *)
|
77
75
|
return super unless
|
78
76
|
/_(params|query)\z/.match?(method_name)
|
@@ -111,7 +109,7 @@ module TypedParams
|
|
111
109
|
class_methods do
|
112
110
|
def typed_params(on: nil, schema: nil, format: nil, **kwargs, &)
|
113
111
|
schema = case schema
|
114
|
-
in Array(Symbol, Symbol
|
112
|
+
in Array(Symbol => namespace, Symbol => key)
|
115
113
|
typed_schemas[namespace, key] || raise(ArgumentError, "schema does not exist: #{namespace.inspect}/#{key.inspect}")
|
116
114
|
in Symbol => key
|
117
115
|
typed_schemas[self, key] || raise(ArgumentError, "schema does not exist: #{key.inspect}")
|
@@ -133,7 +131,7 @@ module TypedParams
|
|
133
131
|
|
134
132
|
def typed_query(on: nil, schema: nil, **kwargs, &)
|
135
133
|
schema = case schema
|
136
|
-
in Array(Symbol, Symbol
|
134
|
+
in Array(Symbol => namespace, Symbol => key)
|
137
135
|
typed_schemas[namespace, key] || raise(ArgumentError, "schema does not exist: #{namespace.inspect}/#{key.inspect}")
|
138
136
|
in Symbol => key
|
139
137
|
typed_schemas[self, key] || raise(ArgumentError, "schema does not exist: #{key.inspect}")
|
@@ -31,9 +31,6 @@ module TypedParams
|
|
31
31
|
:parent
|
32
32
|
|
33
33
|
def parameterize_array_schema(key:, value:)
|
34
|
-
return parameterize_value(key:, value:) unless
|
35
|
-
value.is_a?(Array)
|
36
|
-
|
37
34
|
param = Parameter.new(key:, value: [], schema:, parent:)
|
38
35
|
|
39
36
|
value.each_with_index do |v, i|
|
@@ -56,9 +53,6 @@ module TypedParams
|
|
56
53
|
end
|
57
54
|
|
58
55
|
def parameterize_hash_schema(key:, value:)
|
59
|
-
return parameterize_value(key:, value:) unless
|
60
|
-
value.is_a?(Hash)
|
61
|
-
|
62
56
|
param = Parameter.new(key:, value: {}, schema:, parent:)
|
63
57
|
|
64
58
|
value.each do |k, v|
|
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.0.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-
|
11
|
+
date: 2023-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|