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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4f0ace655411ecec990316600062490e9dae2ac32ce0b3968aa89d557c69446
4
- data.tar.gz: 05ce8679902a636bbecaf227347419c3b721d58af895acde6544174b2c0ef9b2
3
+ metadata.gz: 26b2e5feb7cf9a5dee31da13a451af45b0995fd586593d5ad0aff3619dbae7fb
4
+ data.tar.gz: b0c56e75dfa90e7252d088a848155631bd943707c2d64628d366275c040b319f
5
5
  SHA512:
6
- metadata.gz: 80e39cb77ea2e713d382f70003b3e884dbe85a8e352e73d5859f65f4c3b9632e431521ff3a5cda43ae73f01771a7cfce67cb48d18196423a9617dfa75359d795
7
- data.tar.gz: 2748d2c20ea4226c90038464549b2f26df8935d8093ee282718ae7b22e2409237a1683269ca8fd59a64c1218c29dd31f5b56cce4a1f0310596906bc22401d5fc
6
+ metadata.gz: 2ae0982eefae7e30a6f1d11bc3bf9494c9b94d752f3eb9b130d614a32c19c1b8795c3d2a25c34e50d66171d3a2aa84fbaa4137e6f78454d87da25dfbf30dd478
7
+ data.tar.gz: 6abda1500599b0d982b3808108efc1e9fff777316c5d158bcfe4aadad88612df9ccdc3e63cd2991ecb1c64f72145ae08fd60c9be262d774f14dac29a974a9c86
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
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.
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 = '1.0.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: 1.0.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