typed_params 0.2.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -2
- 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
|
[![Keygen logo](https://github.com/keygen-sh/typed_params/assets/6979737/f2947915-2956-4415-a9c0-5411c388ea96)](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: 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
|