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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33d8c91a43f6350140c1cb3f60e4e08376b87b1abf0d10de9d5a586451bb92c8
4
- data.tar.gz: 81674fe29eb88a8c914dfb573983348cd9bfcd5251ecfb3b8a3f31ec81da4c7b
3
+ metadata.gz: 26b2e5feb7cf9a5dee31da13a451af45b0995fd586593d5ad0aff3619dbae7fb
4
+ data.tar.gz: b0c56e75dfa90e7252d088a848155631bd943707c2d64628d366275c040b319f
5
5
  SHA512:
6
- metadata.gz: 732329c43641f5b8428ef8dda5f888b185715489e2ec1e15fab27bc9e7af8cfa204e0d7a2eb982d30f581d429ad0965b916efdec1766a0cc7ec0d509c08125b6
7
- data.tar.gz: db115e20596f17fe47a6063ba83737256caa9f0f8b81e90ce728787fb230d08ac87e13e0708b6a92c8a671d097fe632a2ed4c29f6f518dc230c841485cd4c561
6
+ metadata.gz: 2ae0982eefae7e30a6f1d11bc3bf9494c9b94d752f3eb9b130d614a32c19c1b8795c3d2a25c34e50d66171d3a2aa84fbaa4137e6f78454d87da25dfbf30dd478
7
+ data.tar.gz: 6abda1500599b0d982b3808108efc1e9fff777316c5d158bcfe4aadad88612df9ccdc3e63cd2991ecb1c64f72145ae08fd60c9be262d774f14dac29a974a9c86
data/CHANGELOG.md CHANGED
@@ -1,9 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.1
4
+
5
+ - Fix namespaced schemas.
6
+
3
7
  ## 1.0.0
4
8
 
5
9
  - Initial release.
6
10
 
7
- ## 0.1.0
11
+ ## 0.2.0
8
12
 
9
- - Test release.
13
+ - Test release.
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/typed_params/controller.rb#L24-L27),
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
- ### Unpermitted parameters
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
- For `.typed_query`, the default is non-strict. This means that any unpermitted parameters
321
- will be ignored.
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 application-level like so:
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::UnpermittedParameterError, err -> {
328
- render_bad_request "unpermitted parameter: #{err.path.to_jsonapi_pointer}"
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::UnpermittedParameterError` error object has the following attributes:
371
+ The `TypedParams::InvalidParameterError` error object has the following attributes:
334
372
 
335
- - `#message` - the error message, e.g. `unpermitted parameter`.
336
- - `#path` - a `Path` object with a pointer to the unpermitted parameter.
337
- - `#source` - either `:params` or `:query`, depending on where the unpermitted parameter came from (i.e. request body vs URL, respectively).
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
- ### Invalid parameters
378
+ ### Unpermitted parameters
340
379
 
341
- When a parameter is provided, but it fails validation (e.g. a type mismatch), a
342
- `TypedParams::InvalidParameterError` error will be raised.
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
- You can rescue this error at the application-level like so:
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
- rescue_from TypedParams::InvalidParameterError, err -> {
349
- render_bad_request "invalid parameter: #{err.message}", parameter: err.path.to_dot_notation
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::InvalidParameterError` error object has the following attributes:
397
+ The `TypedParams::UnpermittedParameterError` error object has the following attributes:
355
398
 
356
- - `#message` - the error message, e.g. `type mismatch (received string expected integer)`.
357
- - `#path` - a `Path` object with a pointer to the invalid parameter.
358
- - `#source` - either `:params` or `:query`, depending on where the invalid parameter came from (i.e. request body vs URL, respectively).
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`](#alias-parameter)
415
+ - [`:as`](#rename-parameter)
370
416
  - [`:noop`](#noop-parameter)
371
- - [`:coerce`](#coerced-parameter)
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
- #### Alias parameter
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
- #### Coerced parameter
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, inclusion: { in: %w[DEBUG INFO WARN ERROR FATAL] }
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, exclusion: { in: %w[DEBUG INFO WARN ERROR FATAL] }
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 :user, type: :string, transform: -> _key, email {
566
- [:user_attributes, { email: }]
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 lamda must return a tuple with the new key and value.
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 time parameter. Must be a `Date`.
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) => namespace, key
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) => namespace, key
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|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TypedParams
4
- VERSION = '0.2.0'
4
+ VERSION = '1.0.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: 0.2.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-07 00:00:00.000000000 Z
11
+ date: 2023-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails