typed_params 0.2.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 +7 -0
- data/CHANGELOG.md +9 -0
- data/CONTRIBUTING.md +33 -0
- data/LICENSE +20 -0
- data/README.md +736 -0
- data/SECURITY.md +8 -0
- data/lib/typed_params/bouncer.rb +34 -0
- data/lib/typed_params/coercer.rb +21 -0
- data/lib/typed_params/configuration.rb +40 -0
- data/lib/typed_params/controller.rb +192 -0
- data/lib/typed_params/formatters/formatter.rb +20 -0
- data/lib/typed_params/formatters/jsonapi.rb +142 -0
- data/lib/typed_params/formatters/rails.rb +31 -0
- data/lib/typed_params/formatters.rb +20 -0
- data/lib/typed_params/handler.rb +24 -0
- data/lib/typed_params/handler_set.rb +19 -0
- data/lib/typed_params/mapper.rb +74 -0
- data/lib/typed_params/namespaced_set.rb +59 -0
- data/lib/typed_params/parameter.rb +100 -0
- data/lib/typed_params/parameterizer.rb +87 -0
- data/lib/typed_params/path.rb +57 -0
- data/lib/typed_params/pipeline.rb +13 -0
- data/lib/typed_params/processor.rb +27 -0
- data/lib/typed_params/schema.rb +290 -0
- data/lib/typed_params/schema_set.rb +7 -0
- data/lib/typed_params/transformer.rb +49 -0
- data/lib/typed_params/transforms/key_alias.rb +16 -0
- data/lib/typed_params/transforms/key_casing.rb +59 -0
- data/lib/typed_params/transforms/nilify_blanks.rb +16 -0
- data/lib/typed_params/transforms/noop.rb +11 -0
- data/lib/typed_params/transforms/transform.rb +11 -0
- data/lib/typed_params/types/array.rb +12 -0
- data/lib/typed_params/types/boolean.rb +33 -0
- data/lib/typed_params/types/date.rb +10 -0
- data/lib/typed_params/types/decimal.rb +10 -0
- data/lib/typed_params/types/float.rb +10 -0
- data/lib/typed_params/types/hash.rb +13 -0
- data/lib/typed_params/types/integer.rb +10 -0
- data/lib/typed_params/types/nil.rb +11 -0
- data/lib/typed_params/types/number.rb +10 -0
- data/lib/typed_params/types/string.rb +10 -0
- data/lib/typed_params/types/symbol.rb +10 -0
- data/lib/typed_params/types/time.rb +20 -0
- data/lib/typed_params/types/type.rb +78 -0
- data/lib/typed_params/types.rb +69 -0
- data/lib/typed_params/validations/exclusion.rb +17 -0
- data/lib/typed_params/validations/format.rb +19 -0
- data/lib/typed_params/validations/inclusion.rb +17 -0
- data/lib/typed_params/validations/length.rb +29 -0
- data/lib/typed_params/validations/validation.rb +18 -0
- data/lib/typed_params/validator.rb +75 -0
- data/lib/typed_params/version.rb +5 -0
- data/lib/typed_params.rb +89 -0
- metadata +124 -0
data/README.md
ADDED
@@ -0,0 +1,736 @@
|
|
1
|
+
# typed_params
|
2
|
+
|
3
|
+
[](https://github.com/keygen-sh/typed_params/actions)
|
4
|
+
[](https://badge.fury.io/rb/typed_params)
|
5
|
+
|
6
|
+
`typed_params` is an alternative to Rails strong parameters for controller params,
|
7
|
+
offering an intuitive DSL for defining structured and strongly-typed controller
|
8
|
+
parameter schemas for Rails APIs.
|
9
|
+
|
10
|
+
This gem was extracted from [Keygen](https://keygen.sh) and is being used in production
|
11
|
+
to serve millions of API requests per day.
|
12
|
+
|
13
|
+
Sponsored by:
|
14
|
+
|
15
|
+
[](https://keygen.sh)
|
16
|
+
|
17
|
+
_An open, source-available software licensing and distribution API._
|
18
|
+
|
19
|
+
Links:
|
20
|
+
|
21
|
+
- [Installing `typed_params`](#installation)
|
22
|
+
- [Supported Ruby versions](#supported-rubies)
|
23
|
+
- [RubyDoc](#documentation)
|
24
|
+
- [Usage](#usage)
|
25
|
+
- [Parameter schemas](#parameter-schemas)
|
26
|
+
- [Query schemas](#query-schemas)
|
27
|
+
- [Defining schemas](#defining-schemas)
|
28
|
+
- [Shared schemas](#shared-schemas)
|
29
|
+
- [Configuration](#configuration)
|
30
|
+
- [Unpermitted parameters](#unpermitted-parameters)
|
31
|
+
- [Invalid parameters](#invalid-parameters)
|
32
|
+
- [Parameter options](#parameter-options)
|
33
|
+
- [Shared options](#shared-options)
|
34
|
+
- [Scalar types](#scalar-types)
|
35
|
+
- [Non-scalar types](#non-scalar-types)
|
36
|
+
- [Custom types](#custom-types)
|
37
|
+
- [Contributing](#contributing)
|
38
|
+
- [License](#license)
|
39
|
+
|
40
|
+
## Installation
|
41
|
+
|
42
|
+
Add this line to your application's `Gemfile`:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
gem 'typed_params'
|
46
|
+
```
|
47
|
+
|
48
|
+
And then execute:
|
49
|
+
|
50
|
+
```bash
|
51
|
+
$ bundle
|
52
|
+
```
|
53
|
+
|
54
|
+
Or install it yourself as:
|
55
|
+
|
56
|
+
```bash
|
57
|
+
$ gem install typed_params
|
58
|
+
```
|
59
|
+
|
60
|
+
## Supported Rubies
|
61
|
+
|
62
|
+
**`typed_params` supports Ruby 3.1 and above.** We encourage you to upgrade if you're
|
63
|
+
on an older version. Ruby 3 provides a lot of great features, like pattern matching and
|
64
|
+
a new shorthand hash syntax.
|
65
|
+
|
66
|
+
## Documentation
|
67
|
+
|
68
|
+
You can find the documentation on [RubyDoc](https://rubydoc.info/github/keygen-sh/typed_params).
|
69
|
+
|
70
|
+
_We're working on improving the docs._
|
71
|
+
|
72
|
+
## Features
|
73
|
+
|
74
|
+
- An intuitive DSL — a breath of fresh air coming from strong parameters.
|
75
|
+
- Define structured, strongly-typed parameter schemas for controllers.
|
76
|
+
- Reuse schemas across controllers by defining named schemas.
|
77
|
+
- Run validations on params, similar to active model validations.
|
78
|
+
- Run transforms on params before they hit your controller.
|
79
|
+
|
80
|
+
## Usage
|
81
|
+
|
82
|
+
`typed_params` can be used to define a parameter schema per-action
|
83
|
+
on controllers.
|
84
|
+
|
85
|
+
To start, include the controller module:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
class ApplicationController < ActionController::API
|
89
|
+
include TypedParams::Controller
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
### Parameter schemas
|
94
|
+
|
95
|
+
To define a parameter schema, you can use the `.typed_params` method.
|
96
|
+
These parameters will be pulled from the request body. It accepts a
|
97
|
+
block containing the schema definition, as well as [options](#parameter-options).
|
98
|
+
|
99
|
+
The parameters will be available inside of the controller action with
|
100
|
+
the following methods:
|
101
|
+
|
102
|
+
- `#{controller_name.singularize}_params`
|
103
|
+
- `typed_params`
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class UsersController < ApplicationController
|
107
|
+
typed_params {
|
108
|
+
param :user, type: :hash do
|
109
|
+
param :first_name, type: :string, optional: true
|
110
|
+
param :last_name, type: :string, optional: true
|
111
|
+
param :email, type: :string
|
112
|
+
param :password, type: :string
|
113
|
+
param :roles, type: :array, if: :admin? do
|
114
|
+
items type: :string
|
115
|
+
end
|
116
|
+
end
|
117
|
+
}
|
118
|
+
def create
|
119
|
+
user = User.new(user_params)
|
120
|
+
|
121
|
+
if user.save
|
122
|
+
render_created user, location: v1_user_url(user)
|
123
|
+
else
|
124
|
+
render_unprocessable_resource user
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
### Query schemas
|
131
|
+
|
132
|
+
To define a query schema, you can use the `.typed_query` method. These
|
133
|
+
parameters will be pulled from the request query parameters. It
|
134
|
+
accepts a block containing the schema definition.
|
135
|
+
|
136
|
+
The parameters will be available inside of the controller action with
|
137
|
+
the following methods:
|
138
|
+
|
139
|
+
- `#{controller_name.singularize}_query`
|
140
|
+
- `#typed_query`
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
class PostsController < ApplicationController
|
144
|
+
typed_query {
|
145
|
+
param :limit, type: :integer, coerce: true, allow_nil: true, optional: true
|
146
|
+
param :page, type: :integer, coerce: true, allow_nil: true, optional: true
|
147
|
+
}
|
148
|
+
def index
|
149
|
+
posts = Post.paginate(
|
150
|
+
post_query.fetch(:limit, 10),
|
151
|
+
post_query.fetch(:page, 1),
|
152
|
+
)
|
153
|
+
|
154
|
+
render_ok posts
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
### Defining schemas
|
160
|
+
|
161
|
+
The easiest way to define a schema is by decorating a specific controller action,
|
162
|
+
which we exemplified above. You can use `.typed_params` or `.typed_query` to
|
163
|
+
decorate a controller action.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
class PostsController < ApplicationController
|
167
|
+
typed_params {
|
168
|
+
param :author_id, type: :integer
|
169
|
+
param :title, type: :string, length: { within: 10..80 }
|
170
|
+
param :content, type: :string, length: { minimum: 100 }
|
171
|
+
param :published_at, type: :time, optional: true, allow_nil: true
|
172
|
+
param :tag_ids, type: :array, optional: true, length: { maximum: 10 } do
|
173
|
+
items type: :integer
|
174
|
+
end
|
175
|
+
}
|
176
|
+
def create
|
177
|
+
# ...
|
178
|
+
end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
As an alternative to decorated schemas, you can define schemas after an action
|
183
|
+
has been defined.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
class PostsController < ApplicationController
|
187
|
+
def create
|
188
|
+
# ...
|
189
|
+
end
|
190
|
+
|
191
|
+
typed_params on: :create do
|
192
|
+
param :author_id, type: :integer
|
193
|
+
param :title, type: :string, length: { within: 10..80 }
|
194
|
+
param :content, type: :string, length: { minimum: 100 }
|
195
|
+
param :published_at, type: :time, optional: true, allow_nil: true
|
196
|
+
param :tag_ids, type: :array, optional: true, length: { maximum: 10 } do
|
197
|
+
items type: :integer
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
By default, all root schemas are a [`:hash`](#hash-type) schema. This is because both
|
204
|
+
`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),
|
206
|
+
so that you could use a top-level array schema. You can create nested schemas via
|
207
|
+
the [`:hash`](#hash-type) and [`:array`](#array-type) types.
|
208
|
+
|
209
|
+
### Shared schemas
|
210
|
+
|
211
|
+
If you need to share a specific schema between multiple actions, you can define
|
212
|
+
a named schema.
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
class PostsController < ApplicationController
|
216
|
+
typed_schema :post do
|
217
|
+
param :author_id, type: :integer
|
218
|
+
param :title, type: :string, length: { within: 10..80 }
|
219
|
+
param :content, type: :string, length: { minimum: 100 }
|
220
|
+
param :published_at, type: :time, optional: true, allow_nil: true
|
221
|
+
param :tag_ids, type: :array, optional: true, length: { maximum: 10 } do
|
222
|
+
items type: :integer
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
typed_params schema: :post
|
227
|
+
def create
|
228
|
+
# ...
|
229
|
+
end
|
230
|
+
|
231
|
+
typed_params schema: :post
|
232
|
+
def update
|
233
|
+
# ...
|
234
|
+
end
|
235
|
+
end
|
236
|
+
```
|
237
|
+
|
238
|
+
### Configuration
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
TypedParams.configure do |config|
|
242
|
+
# Ignore nil params that are marked optional and non-nil in the schema.
|
243
|
+
#
|
244
|
+
# For example, given the following schema:
|
245
|
+
#
|
246
|
+
# typed_params {
|
247
|
+
# param :optional_key, type: :string, optional: true
|
248
|
+
# param :required_key, type: :string
|
249
|
+
# }
|
250
|
+
#
|
251
|
+
# And the following curl request:
|
252
|
+
#
|
253
|
+
# curl -X POST http://localhost:3000 -d '{"optional_key":null,"required_key":"value"}'
|
254
|
+
#
|
255
|
+
# Within the controller, the params would be:
|
256
|
+
#
|
257
|
+
# puts typed_params # => { required_key: 'value' }
|
258
|
+
#
|
259
|
+
config.ignore_nil_optionals = true
|
260
|
+
|
261
|
+
# Key transformation applied to the parameters after validation.
|
262
|
+
#
|
263
|
+
# One of:
|
264
|
+
#
|
265
|
+
# - :underscore
|
266
|
+
# - :camel
|
267
|
+
# - :lower_camel
|
268
|
+
# - :dash
|
269
|
+
# - nil
|
270
|
+
#
|
271
|
+
# For example, given the following schema:
|
272
|
+
#
|
273
|
+
# typed_params {
|
274
|
+
# param :someKey, type: :string
|
275
|
+
# }
|
276
|
+
#
|
277
|
+
# And the following curl request:
|
278
|
+
#
|
279
|
+
# curl -X POST http://localhost:3000 -d '{"someKey":"value"}'
|
280
|
+
#
|
281
|
+
# Within the controller, the params would be:
|
282
|
+
#
|
283
|
+
# puts typed_params # => { some_key: 'value' }
|
284
|
+
#
|
285
|
+
config.key_transform = :underscore
|
286
|
+
|
287
|
+
# Path transformation applied to error paths e.g. UnpermittedParameterError.
|
288
|
+
#
|
289
|
+
# One of:
|
290
|
+
#
|
291
|
+
# - :underscore
|
292
|
+
# - :camel
|
293
|
+
# - :lower_camel
|
294
|
+
# - :dash
|
295
|
+
# - nil
|
296
|
+
#
|
297
|
+
# For example, given the following schema:
|
298
|
+
#
|
299
|
+
# typed_params {
|
300
|
+
# param :parent_key, type: :hash do
|
301
|
+
# param :child_key, type: :string
|
302
|
+
# end
|
303
|
+
# }
|
304
|
+
#
|
305
|
+
# With an invalid `child_key`, the path would be:
|
306
|
+
#
|
307
|
+
# rescue_from TypedParams::UnpermittedParameterError, err -> {
|
308
|
+
# puts err.path.to_s # => parentKey.childKey
|
309
|
+
# }
|
310
|
+
#
|
311
|
+
config.path_transform = :lower_camel
|
312
|
+
end
|
313
|
+
```
|
314
|
+
|
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.
|
319
|
+
|
320
|
+
For `.typed_query`, the default is non-strict. This means that any unpermitted parameters
|
321
|
+
will be ignored.
|
322
|
+
|
323
|
+
You can rescue this error at the application-level like so:
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
class ApplicationController < ActionController::API
|
327
|
+
rescue_from TypedParams::UnpermittedParameterError, err -> {
|
328
|
+
render_bad_request "unpermitted parameter: #{err.path.to_jsonapi_pointer}"
|
329
|
+
}
|
330
|
+
end
|
331
|
+
```
|
332
|
+
|
333
|
+
The `TypedParams::UnpermittedParameterError` error object has the following attributes:
|
334
|
+
|
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).
|
338
|
+
|
339
|
+
### Invalid parameters
|
340
|
+
|
341
|
+
When a parameter is provided, but it fails validation (e.g. a type mismatch), a
|
342
|
+
`TypedParams::InvalidParameterError` error will be raised.
|
343
|
+
|
344
|
+
You can rescue this error at the application-level like so:
|
345
|
+
|
346
|
+
```ruby
|
347
|
+
class ApplicationController < ActionController::API
|
348
|
+
rescue_from TypedParams::InvalidParameterError, err -> {
|
349
|
+
render_bad_request "invalid parameter: #{err.message}", parameter: err.path.to_dot_notation
|
350
|
+
}
|
351
|
+
end
|
352
|
+
```
|
353
|
+
|
354
|
+
The `TypedParams::InvalidParameterError` error object has the following attributes:
|
355
|
+
|
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).
|
359
|
+
|
360
|
+
### Parameter options
|
361
|
+
|
362
|
+
Parameters can have validations, transforms, and more.
|
363
|
+
|
364
|
+
- [`:key`](#parameter-key)
|
365
|
+
- [`:type`](#parameter-type)
|
366
|
+
- [`:strict`](#strict-parameter)
|
367
|
+
- [`:optional`](#optional-parameter)
|
368
|
+
- [`:if` and `:unless`](#conditional-parameter)
|
369
|
+
- [`:as`](#alias-parameter)
|
370
|
+
- [`:noop`](#noop-parameter)
|
371
|
+
- [`:coerce`](#coerced-parameter)
|
372
|
+
- [`:allow_blank`](#allow-blank)
|
373
|
+
- [`:allow_nil`](#allow-nil)
|
374
|
+
- [`:allow_non_scalars`](#allow-non-scalars)
|
375
|
+
- [`:nilify_blanks`](#nilify-blanks)
|
376
|
+
- [`:inclusion`](#inclusion-validation)
|
377
|
+
- [`:exclusion`](#exclusion-validation)
|
378
|
+
- [`:format`](#format-validation)
|
379
|
+
- [`:length`](#length-validation)
|
380
|
+
- [`:transform`](#transform-parameter)
|
381
|
+
- [`:validate`](#validate-parameter)
|
382
|
+
|
383
|
+
#### Parameter key
|
384
|
+
|
385
|
+
The parameter's key.
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
param :foo
|
389
|
+
```
|
390
|
+
|
391
|
+
This is required.
|
392
|
+
|
393
|
+
#### Parameter type
|
394
|
+
|
395
|
+
The parameter's type. Please see [Types](#scalar-types) for more information. Some
|
396
|
+
types may accept a block, e.g. `:hash` and `:array`.
|
397
|
+
|
398
|
+
```ruby
|
399
|
+
param :email, type: :string
|
400
|
+
```
|
401
|
+
|
402
|
+
This is required.
|
403
|
+
|
404
|
+
#### Strict parameter
|
405
|
+
|
406
|
+
When `true`, a `TypedParams::UnpermittedParameterError` error is raised for
|
407
|
+
unpermitted parameters. When `false`, unpermitted parameters are ignored.
|
408
|
+
|
409
|
+
```ruby
|
410
|
+
param :user, type: :hash, strict: true do
|
411
|
+
# ...
|
412
|
+
end
|
413
|
+
```
|
414
|
+
|
415
|
+
By default, the entire `.typed_params` schema is strict, and `.typed_query` is not.
|
416
|
+
|
417
|
+
#### Optional parameter
|
418
|
+
|
419
|
+
The parameter is optional. An invalid parameter error will not be raised in its absence.
|
420
|
+
|
421
|
+
```ruby
|
422
|
+
param :first_name, type: :string, optional: true
|
423
|
+
```
|
424
|
+
|
425
|
+
By default, parameters are required.
|
426
|
+
|
427
|
+
#### Conditional parameter
|
428
|
+
|
429
|
+
You can define conditional parameters using `:if` and `:unless`. The parameter will
|
430
|
+
only be evaluated when the condition to `true`.
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
param :role, type: :string, if: -> { admin? }
|
434
|
+
param :role, type: :string, if: :admin?
|
435
|
+
param :role, type: :string, unless: -> { guest? }
|
436
|
+
param :role, type: :string, unless: :guest?
|
437
|
+
```
|
438
|
+
|
439
|
+
The lambda will be evaled within the current controller context.
|
440
|
+
|
441
|
+
#### Alias parameter
|
442
|
+
|
443
|
+
Apply a transformation that renames the parameter.
|
444
|
+
|
445
|
+
```ruby
|
446
|
+
param :user, type: :integer, as: :user_id
|
447
|
+
|
448
|
+
typed_params # => { user_id: '...' }
|
449
|
+
```
|
450
|
+
|
451
|
+
In this example, the parameter would be accepted as `:user`, but renamed
|
452
|
+
to `:user_id` for use inside of the controller.
|
453
|
+
|
454
|
+
#### Noop parameter
|
455
|
+
|
456
|
+
The parameter is accepted but immediately thrown out.
|
457
|
+
|
458
|
+
```ruby
|
459
|
+
param :foo, type: :string, noop: true
|
460
|
+
```
|
461
|
+
|
462
|
+
By default, this is `false`.
|
463
|
+
|
464
|
+
#### Coerced parameter
|
465
|
+
|
466
|
+
The parameter will be coerced if its type is coercible and the parameter has a
|
467
|
+
type mismatch. The coercion can fail, e.g. `:integer` to `:hash`, and if it does,
|
468
|
+
a `TypedParams::InvalidParameterError` will be raised.
|
469
|
+
|
470
|
+
```ruby
|
471
|
+
param :age, type: :integer, coerce: true
|
472
|
+
```
|
473
|
+
|
474
|
+
The default is `false`.
|
475
|
+
|
476
|
+
#### Allow blank
|
477
|
+
|
478
|
+
The parameter can be `#blank?`.
|
479
|
+
|
480
|
+
```ruby
|
481
|
+
param :title, type: :string, allow_blank: true
|
482
|
+
```
|
483
|
+
|
484
|
+
By default, blank params are rejected with a `TypedParams::InvalidParameterError`
|
485
|
+
error.
|
486
|
+
|
487
|
+
#### Allow nil
|
488
|
+
|
489
|
+
The parameter can be `#nil?`.
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
param :tag, type: :string, allow_nil: true
|
493
|
+
```
|
494
|
+
|
495
|
+
By default, nil params are rejected with a `TypedParams::InvalidParameterError`
|
496
|
+
error.
|
497
|
+
|
498
|
+
#### Allow non-scalars
|
499
|
+
|
500
|
+
Only applicable to the `:hash` type and its subtypes. Allow non-scalar values in
|
501
|
+
a `:hash` parameter. Scalar types can be found under [Types](#scalar-types).
|
502
|
+
|
503
|
+
```ruby
|
504
|
+
param :metadata, type: :hash, allow_non_scalars: true
|
505
|
+
```
|
506
|
+
|
507
|
+
By default, non-scalar parameters are rejected with a `TypedParams::InvalidParameterError`
|
508
|
+
error.
|
509
|
+
|
510
|
+
#### Nilify blanks
|
511
|
+
|
512
|
+
Automatically convert `#blank?` values to `nil`.
|
513
|
+
|
514
|
+
```ruby
|
515
|
+
param :phone_number, type: :string, nilify_blanks: true
|
516
|
+
```
|
517
|
+
|
518
|
+
By default, this is disabled.
|
519
|
+
|
520
|
+
#### Inclusion validation
|
521
|
+
|
522
|
+
The parameter must be included in the array or range.
|
523
|
+
|
524
|
+
```ruby
|
525
|
+
param :log_level, type: :string, inclusion: { in: %w[DEBUG INFO WARN ERROR FATAL] }
|
526
|
+
param :priority, type: :integer, inclusion: { in: 0..9 }
|
527
|
+
```
|
528
|
+
|
529
|
+
#### Exclusion validation
|
530
|
+
|
531
|
+
The parameter must be excluded from the array or range.
|
532
|
+
|
533
|
+
```ruby
|
534
|
+
param :custom_log_level, type: :string, exclusion: { in: %w[DEBUG INFO WARN ERROR FATAL] }
|
535
|
+
param :custom_priority, type: :integer, exclusion: { in: 0..9 }
|
536
|
+
```
|
537
|
+
|
538
|
+
#### Format validation
|
539
|
+
|
540
|
+
The parameter must be a certain regular expression format.
|
541
|
+
|
542
|
+
```ruby
|
543
|
+
param :first_name, type: :string, format: { with: /foo/ }
|
544
|
+
param :last_name, type: :string, format: { without: /bar/ }
|
545
|
+
```
|
546
|
+
|
547
|
+
#### Length validation
|
548
|
+
|
549
|
+
The parameter must be a certain length.
|
550
|
+
|
551
|
+
```ruby
|
552
|
+
param :content, type: :string, length: { minimum: 100 }
|
553
|
+
param :title, type: :string, length: { maximum: 10 }
|
554
|
+
param :tweet, type: :string, length: { within: ..160 }
|
555
|
+
param :odd, type: :string, length: { in: [2, 4, 6, 8] }
|
556
|
+
param :ten, type: :string, length: { is: 10 }
|
557
|
+
```
|
558
|
+
|
559
|
+
#### Transform parameter
|
560
|
+
|
561
|
+
Transform the parameter using a lambda. This is commonly used to transform a
|
562
|
+
parameter into a nested attributes hash or array.
|
563
|
+
|
564
|
+
```ruby
|
565
|
+
param :user, type: :string, transform: -> _key, email {
|
566
|
+
[:user_attributes, { email: }]
|
567
|
+
}
|
568
|
+
```
|
569
|
+
|
570
|
+
The lambda must accept a key (the current parameter key), and a value (the
|
571
|
+
current parameter value).
|
572
|
+
|
573
|
+
The lamda must return a tuple with the new key and value.
|
574
|
+
|
575
|
+
#### Validate parameter
|
576
|
+
|
577
|
+
Define a custom validation for the parameter, outside of the default
|
578
|
+
validations.
|
579
|
+
|
580
|
+
```ruby
|
581
|
+
param :user, type: :integer, validate: -> id {
|
582
|
+
User.exists?(id)
|
583
|
+
}
|
584
|
+
```
|
585
|
+
|
586
|
+
The lambda should accept a value and return a boolean. When the boolean
|
587
|
+
evaluates to `false`, a `TypedParams::InvalidParameterError` will
|
588
|
+
be raised.
|
589
|
+
|
590
|
+
### Shared options
|
591
|
+
|
592
|
+
You can define a set of options that will be applied to immediate
|
593
|
+
children parameters (i.e. not grandchilden).
|
594
|
+
|
595
|
+
```ruby
|
596
|
+
with if: :admin? do
|
597
|
+
param :referrer, type: :string, optional: true
|
598
|
+
param :role, type: :string
|
599
|
+
end
|
600
|
+
```
|
601
|
+
|
602
|
+
### Scalar types
|
603
|
+
|
604
|
+
- [`:string`](#string-type)
|
605
|
+
- [`:boolean`](#boolean-type)
|
606
|
+
- [`:integer`](#integer-type)
|
607
|
+
- [`:float`](#float-type)
|
608
|
+
- [`:decimal`](#decimal-type)
|
609
|
+
- [`:number`](#number-type)
|
610
|
+
- [`:symbol`](#symbol-type)
|
611
|
+
- [`:time`](#time-type)
|
612
|
+
- [`:date`](#date-type)
|
613
|
+
|
614
|
+
#### String type
|
615
|
+
|
616
|
+
Defines a string parameter. Must be a `String`.
|
617
|
+
|
618
|
+
#### Boolean type
|
619
|
+
|
620
|
+
Defines a boolean parameter. Must be `TrueClass` or `FalseClass`.
|
621
|
+
|
622
|
+
#### Integer type
|
623
|
+
|
624
|
+
Defines an integer parameter. Must be an `Integer`.
|
625
|
+
|
626
|
+
#### Float type
|
627
|
+
|
628
|
+
Defines a float parameter. Must be a `Float`.
|
629
|
+
|
630
|
+
#### Decimal type
|
631
|
+
|
632
|
+
Defines a decimal parameter. Must be a `BigDecimal`.
|
633
|
+
|
634
|
+
#### Number type
|
635
|
+
|
636
|
+
Defines a number parameter. Must be either an `Integer`, a `Float`, or a `BigDecimal`.
|
637
|
+
|
638
|
+
#### Symbol type
|
639
|
+
|
640
|
+
Defines a symbol parameter. Must be a `Symbol`.
|
641
|
+
|
642
|
+
#### Time type
|
643
|
+
|
644
|
+
Defines a time parameter. Must be a `Time`.
|
645
|
+
|
646
|
+
#### Date type
|
647
|
+
|
648
|
+
Defines a time parameter. Must be a `Date`.
|
649
|
+
|
650
|
+
### Non-scalar types
|
651
|
+
|
652
|
+
- [`:array`](#array-type)
|
653
|
+
- [`:hash`](#hash-type)
|
654
|
+
|
655
|
+
#### Array type
|
656
|
+
|
657
|
+
Defines an array parameter. Must be an `Array`.
|
658
|
+
|
659
|
+
Arrays are a special type. They can accept a block that defines its item types,
|
660
|
+
which may be a nested schema.
|
661
|
+
|
662
|
+
```ruby
|
663
|
+
# array of hashes
|
664
|
+
param :boundless_array, type: :array do
|
665
|
+
item type: :hash do
|
666
|
+
# ...
|
667
|
+
end
|
668
|
+
end
|
669
|
+
# array of 1 integer and 1 string
|
670
|
+
param :bounded_array, type: :array do
|
671
|
+
item type: :integer
|
672
|
+
item type: :string
|
673
|
+
end
|
674
|
+
```
|
675
|
+
|
676
|
+
#### Hash type
|
677
|
+
|
678
|
+
Defines a hash parameter. Must be a `Hash`.
|
679
|
+
|
680
|
+
Hashes are a special type. They can accept a block that defines a nested schema.
|
681
|
+
|
682
|
+
```ruby
|
683
|
+
# define a nested schema
|
684
|
+
param :parent, type: :hash do
|
685
|
+
param :child, type: :hash do
|
686
|
+
# ...
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
# non-schema hash
|
691
|
+
param :only_scalars, type: :hash
|
692
|
+
param :non_scalars_too, type: :hash, allow_non_scalars: true
|
693
|
+
```
|
694
|
+
|
695
|
+
### Custom types
|
696
|
+
|
697
|
+
You may register custom types that can be utilized in your schemas.
|
698
|
+
|
699
|
+
Each type consists of, at minimum, a `match:` lambda. For more usage
|
700
|
+
examples, see [the default types](https://github.com/keygen-sh/typed_params/tree/master/lib/typed_params/types).
|
701
|
+
|
702
|
+
```ruby
|
703
|
+
TypedParams.types.register(:metadata,
|
704
|
+
archetype: :hash,
|
705
|
+
match: -> value {
|
706
|
+
return false unless
|
707
|
+
value.is_a?(Hash)
|
708
|
+
|
709
|
+
# Metadata can have one layer of nested arrays/hashes
|
710
|
+
value.values.all? { |v|
|
711
|
+
case v
|
712
|
+
when Hash
|
713
|
+
v.values.none? { _1.is_a?(Array) || _1.is_a?(Hash) }
|
714
|
+
when Array
|
715
|
+
v.none? { _1.is_a?(Array) || _1.is_a?(Hash) }
|
716
|
+
else
|
717
|
+
true
|
718
|
+
end
|
719
|
+
}
|
720
|
+
},
|
721
|
+
)
|
722
|
+
```
|
723
|
+
|
724
|
+
## Is it any good?
|
725
|
+
|
726
|
+
[Yes.](https://news.ycombinator.com/item?id=3067434)
|
727
|
+
|
728
|
+
## Contributing
|
729
|
+
|
730
|
+
If you have an idea, or have discovered a bug, please open an issue or create a pull request.
|
731
|
+
|
732
|
+
For security issues, please see [`SECURITY.md`](https://github.com/keygen-sh/typed_params/blob/master/SECURITY.md)
|
733
|
+
|
734
|
+
## License
|
735
|
+
|
736
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|