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
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'typed_params/path'
|
4
|
+
|
5
|
+
module TypedParams
|
6
|
+
class Parameter
|
7
|
+
attr_accessor :key,
|
8
|
+
:value
|
9
|
+
|
10
|
+
attr_reader :schema,
|
11
|
+
:parent
|
12
|
+
|
13
|
+
def initialize(key:, value:, schema:, parent: nil)
|
14
|
+
@key = key
|
15
|
+
@value = value
|
16
|
+
@schema = schema
|
17
|
+
@parent = parent
|
18
|
+
end
|
19
|
+
|
20
|
+
def array? = Types.array?(value)
|
21
|
+
def hash? = Types.hash?(value)
|
22
|
+
def scalar? = Types.scalar?(value)
|
23
|
+
def parent? = parent.present?
|
24
|
+
|
25
|
+
def path
|
26
|
+
key = @key == ROOT ? nil : @key
|
27
|
+
|
28
|
+
@path ||= Path.new(*parent&.path&.keys, *key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def key?(key) = keys.include?(key)
|
32
|
+
alias :has_key? :key?
|
33
|
+
|
34
|
+
def keys?(*keys) = keys.all? { key?(_1) }
|
35
|
+
alias :has_keys? :keys?
|
36
|
+
|
37
|
+
def keys
|
38
|
+
return [] if
|
39
|
+
schema.children.blank?
|
40
|
+
|
41
|
+
case value
|
42
|
+
when Array
|
43
|
+
(0...value.size).to_a
|
44
|
+
when Hash
|
45
|
+
value.keys
|
46
|
+
else
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete
|
52
|
+
raise NotImplementedError, "cannot delete param: #{key.inspect}" unless
|
53
|
+
parent?
|
54
|
+
|
55
|
+
case parent.value
|
56
|
+
when Array
|
57
|
+
parent.value.delete(self)
|
58
|
+
when Hash
|
59
|
+
parent.value.delete(
|
60
|
+
parent.value.key(self),
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def unwrap(formatter: schema.formatter, controller: nil)
|
66
|
+
v = case value
|
67
|
+
when Hash
|
68
|
+
value.transform_values { _1.respond_to?(:unwrap) ? _1.unwrap : _1 }
|
69
|
+
when Array
|
70
|
+
value.map { _1.respond_to?(:unwrap) ? _1.unwrap : _1 }
|
71
|
+
else
|
72
|
+
value.respond_to?(:unwrap) ? value.unwrap : value
|
73
|
+
end
|
74
|
+
|
75
|
+
if formatter.present?
|
76
|
+
v = case formatter.arity
|
77
|
+
when 2
|
78
|
+
formatter.call(v, controller:)
|
79
|
+
when 1
|
80
|
+
formatter.call(v)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
v
|
85
|
+
end
|
86
|
+
|
87
|
+
# Delegate everything else to the value
|
88
|
+
def respond_to_missing?(method_name, ...) = value.respond_to?(method_name, ...)
|
89
|
+
def method_missing(method_name, ...) = value.send(method_name, ...)
|
90
|
+
|
91
|
+
def deconstruct_keys(keys) = { key:, value: }
|
92
|
+
def deconstruct = value
|
93
|
+
|
94
|
+
def inspect
|
95
|
+
value = unwrap(formatter: nil)
|
96
|
+
|
97
|
+
"#<#{self.class.name} key=#{key.inspect} value=#{value.inspect}>"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TypedParams
|
4
|
+
class Parameterizer
|
5
|
+
def initialize(schema:, parent: nil)
|
6
|
+
@schema = schema
|
7
|
+
@parent = parent
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(key: ROOT, value:)
|
11
|
+
return value if
|
12
|
+
value.is_a?(Parameter)
|
13
|
+
|
14
|
+
return nil if
|
15
|
+
key == ROOT &&
|
16
|
+
value.nil?
|
17
|
+
|
18
|
+
case schema.children
|
19
|
+
when Array
|
20
|
+
parameterize_array_schema(key:, value:)
|
21
|
+
when Hash
|
22
|
+
parameterize_hash_schema(key:, value:)
|
23
|
+
else
|
24
|
+
parameterize_value(key:, value:)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :schema,
|
31
|
+
:parent
|
32
|
+
|
33
|
+
def parameterize_array_schema(key:, value:)
|
34
|
+
return parameterize_value(key:, value:) unless
|
35
|
+
value.is_a?(Array)
|
36
|
+
|
37
|
+
param = Parameter.new(key:, value: [], schema:, parent:)
|
38
|
+
|
39
|
+
value.each_with_index do |v, i|
|
40
|
+
unless schema.children.nil?
|
41
|
+
child = schema.children.fetch(i) { schema.boundless? ? schema.children.first : nil }
|
42
|
+
if child.nil?
|
43
|
+
raise UnpermittedParameterError.new('unpermitted parameter', path: Path.new(*param.path.keys, i), source: schema.source) if
|
44
|
+
schema.strict?
|
45
|
+
|
46
|
+
next
|
47
|
+
end
|
48
|
+
|
49
|
+
param << Parameterizer.new(schema: child, parent: param).call(key: i, value: v)
|
50
|
+
else
|
51
|
+
param << Parameter.new(key: i, value: v, schema:, parent: param)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
param
|
56
|
+
end
|
57
|
+
|
58
|
+
def parameterize_hash_schema(key:, value:)
|
59
|
+
return parameterize_value(key:, value:) unless
|
60
|
+
value.is_a?(Hash)
|
61
|
+
|
62
|
+
param = Parameter.new(key:, value: {}, schema:, parent:)
|
63
|
+
|
64
|
+
value.each do |k, v|
|
65
|
+
unless schema.children.nil?
|
66
|
+
child = schema.children.fetch(k) { nil }
|
67
|
+
if child.nil?
|
68
|
+
raise UnpermittedParameterError.new('unpermitted parameter', path: Path.new(*param.path.keys, k), source: schema.source) if
|
69
|
+
schema.strict?
|
70
|
+
|
71
|
+
next
|
72
|
+
end
|
73
|
+
|
74
|
+
param[k] = Parameterizer.new(schema: child, parent: param).call(key: k, value: v)
|
75
|
+
else
|
76
|
+
param[k] = Parameter.new(key: k, value: v, schema:, parent: param)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
param
|
81
|
+
end
|
82
|
+
|
83
|
+
def parameterize_value(key:, value:)
|
84
|
+
Parameter.new(key:, value:, schema:, parent:)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TypedParams
|
4
|
+
class Path
|
5
|
+
attr_reader :keys
|
6
|
+
|
7
|
+
def initialize(*keys, casing: TypedParams.config.path_transform)
|
8
|
+
@casing = casing
|
9
|
+
@keys = keys
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_json_pointer = '/' + keys.map { transform_key(_1) }.join('/')
|
13
|
+
def to_dot_notation = keys.map { transform_key(_1) }.join('.')
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
keys.map { transform_key(_1) }.reduce(+'') do |s, key|
|
17
|
+
next s << key if s.blank?
|
18
|
+
|
19
|
+
case key
|
20
|
+
when Integer
|
21
|
+
s << "[#{key}]"
|
22
|
+
else
|
23
|
+
s << ".#{key}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
"#<#{self.class.name}: #{to_s.inspect}>"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :casing
|
35
|
+
|
36
|
+
def transform_string(str)
|
37
|
+
case casing
|
38
|
+
when :underscore
|
39
|
+
str.underscore
|
40
|
+
when :camel
|
41
|
+
str.underscore.camelize
|
42
|
+
when :lower_camel
|
43
|
+
str.underscore.camelize(:lower)
|
44
|
+
when :dash
|
45
|
+
str.underscore.dasherize
|
46
|
+
else
|
47
|
+
str
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def transform_key(key)
|
52
|
+
return key if key.is_a?(Integer)
|
53
|
+
|
54
|
+
transform_string(key.to_s)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TypedParams
|
4
|
+
class Processor
|
5
|
+
def initialize(schema:, controller: nil)
|
6
|
+
@controller = controller
|
7
|
+
@schema = schema
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(value)
|
11
|
+
params = Parameterizer.new(schema:).call(value:)
|
12
|
+
pipeline = Pipeline.new
|
13
|
+
|
14
|
+
pipeline << Bouncer.new(controller:, schema:)
|
15
|
+
pipeline << Coercer.new(schema:)
|
16
|
+
pipeline << Validator.new(schema:)
|
17
|
+
pipeline << Transformer.new(controller:, schema:)
|
18
|
+
|
19
|
+
pipeline.call(params)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :controller,
|
25
|
+
:schema
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,290 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TypedParams
|
4
|
+
class Schema
|
5
|
+
attr_reader :validations,
|
6
|
+
:transforms,
|
7
|
+
:formatter,
|
8
|
+
:parent,
|
9
|
+
:children,
|
10
|
+
:source,
|
11
|
+
:type,
|
12
|
+
:key,
|
13
|
+
:if,
|
14
|
+
:unless
|
15
|
+
|
16
|
+
def initialize(
|
17
|
+
controller: nil,
|
18
|
+
source: nil,
|
19
|
+
strict: true,
|
20
|
+
parent: nil,
|
21
|
+
type: :hash,
|
22
|
+
key: nil,
|
23
|
+
optional: false,
|
24
|
+
coerce: false,
|
25
|
+
allow_blank: false,
|
26
|
+
allow_nil: false,
|
27
|
+
allow_non_scalars: false,
|
28
|
+
nilify_blanks: false,
|
29
|
+
noop: false,
|
30
|
+
inclusion: nil,
|
31
|
+
exclusion: nil,
|
32
|
+
format: nil,
|
33
|
+
length: nil,
|
34
|
+
transform: nil,
|
35
|
+
validate: nil,
|
36
|
+
if: nil,
|
37
|
+
unless: nil,
|
38
|
+
as: nil,
|
39
|
+
casing: TypedParams.config.key_transform,
|
40
|
+
&block
|
41
|
+
)
|
42
|
+
key ||= ROOT
|
43
|
+
|
44
|
+
raise ArgumentError, 'key is required for child schema' if
|
45
|
+
key == ROOT && parent.present?
|
46
|
+
|
47
|
+
raise ArgumentError, 'root cannot be null' if
|
48
|
+
key == ROOT && allow_nil
|
49
|
+
|
50
|
+
raise ArgumentError, 'source must be one of: :params or :query' unless
|
51
|
+
source.nil? || source == :params || source == :query
|
52
|
+
|
53
|
+
raise ArgumentError, 'inclusion must be a hash with :in key' unless
|
54
|
+
inclusion.nil? || inclusion.is_a?(Hash) && inclusion.key?(:in)
|
55
|
+
|
56
|
+
raise ArgumentError, 'exclusion must be a hash with :in key' unless
|
57
|
+
exclusion.nil? || exclusion.is_a?(Hash) && exclusion.key?(:in)
|
58
|
+
|
59
|
+
raise ArgumentError, 'format must be a hash with :with or :without keys (but not both)' unless
|
60
|
+
format.nil? || format.is_a?(Hash) && (
|
61
|
+
format.key?(:with) ^
|
62
|
+
format.key?(:without)
|
63
|
+
)
|
64
|
+
|
65
|
+
raise ArgumentError, 'length must be a hash with :minimum, :maximum, :within, :in, or :is keys (but not multiple)' unless
|
66
|
+
length.nil? || length.is_a?(Hash) && (
|
67
|
+
length.key?(:minimum) ^
|
68
|
+
length.key?(:maximum) ^
|
69
|
+
length.key?(:within) ^
|
70
|
+
length.key?(:in) ^
|
71
|
+
length.key?(:is)
|
72
|
+
)
|
73
|
+
|
74
|
+
@controller = controller
|
75
|
+
@source = source
|
76
|
+
@type = Types[type]
|
77
|
+
@strict = strict
|
78
|
+
@parent = parent
|
79
|
+
@key = key
|
80
|
+
@optional = optional
|
81
|
+
@coerce = coerce && @type.coercable?
|
82
|
+
@allow_blank = key == ROOT || allow_blank
|
83
|
+
@allow_nil = allow_nil
|
84
|
+
@allow_non_scalars = allow_non_scalars
|
85
|
+
@nilify_blanks = nilify_blanks
|
86
|
+
@noop = noop
|
87
|
+
@inclusion = inclusion
|
88
|
+
@exclusion = exclusion
|
89
|
+
@format = format
|
90
|
+
@length = length
|
91
|
+
@casing = casing
|
92
|
+
@transform = transform
|
93
|
+
@children = nil
|
94
|
+
@if = binding.local_variable_get(:if)
|
95
|
+
@unless = binding.local_variable_get(:unless)
|
96
|
+
@formatter = nil
|
97
|
+
@options = {}
|
98
|
+
|
99
|
+
# Validations
|
100
|
+
@validations = []
|
101
|
+
|
102
|
+
@validations << Validations::Inclusion.new(inclusion) if
|
103
|
+
inclusion.present?
|
104
|
+
|
105
|
+
@validations << Validations::Exclusion.new(exclusion) if
|
106
|
+
exclusion.present?
|
107
|
+
|
108
|
+
@validations << Validations::Format.new(format) if
|
109
|
+
format.present?
|
110
|
+
|
111
|
+
@validations << Validations::Length.new(length) if
|
112
|
+
length.present?
|
113
|
+
|
114
|
+
@validations << Validations::Validation.wrap(validate) if
|
115
|
+
validate.present?
|
116
|
+
|
117
|
+
# Transforms
|
118
|
+
@transforms = []
|
119
|
+
|
120
|
+
@transforms << Transforms::KeyAlias.new(as) if
|
121
|
+
as.present?
|
122
|
+
|
123
|
+
@transforms << Transforms::NilifyBlanks.new if
|
124
|
+
nilify_blanks
|
125
|
+
|
126
|
+
@transforms << Transforms::Transform.wrap(transform) if
|
127
|
+
transform.present?
|
128
|
+
|
129
|
+
@transforms << Transforms::KeyCasing.new(casing) if
|
130
|
+
casing.present?
|
131
|
+
|
132
|
+
@transforms << Transforms::Noop.new if
|
133
|
+
noop
|
134
|
+
|
135
|
+
raise ArgumentError, "type #{type} is a not registered type" if
|
136
|
+
@type.nil?
|
137
|
+
|
138
|
+
if block_given?
|
139
|
+
raise ArgumentError, "type #{@type} does not accept a block" if
|
140
|
+
@type.present? && !@type.accepts_block?
|
141
|
+
|
142
|
+
@children = case
|
143
|
+
when Types.array?(@type)
|
144
|
+
[]
|
145
|
+
when Types.hash?(@type)
|
146
|
+
{}
|
147
|
+
end
|
148
|
+
|
149
|
+
self.instance_exec &block
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# format defines the final output format for the schema, transforming
|
155
|
+
# the params from an input format to an output format, e.g. a JSONAPI
|
156
|
+
# document to Rails' standard params format. This also applies the
|
157
|
+
# formatter's decorators onto the controller.
|
158
|
+
def format(format)
|
159
|
+
raise NotImplementedError, 'cannot define format for child schema' if
|
160
|
+
child?
|
161
|
+
|
162
|
+
formatter = Formatters[format]
|
163
|
+
|
164
|
+
raise ArgumentError, "invalid format: #{format.inspect}" if
|
165
|
+
formatter.nil?
|
166
|
+
|
167
|
+
# Apply the formatter's decorators onto the controller.
|
168
|
+
controller.instance_exec(&formatter.decorator) if
|
169
|
+
controller.present? && formatter.decorator?
|
170
|
+
|
171
|
+
@formatter = formatter
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# with defines a set of options to use for all direct children of the
|
176
|
+
# schema defined within the block.
|
177
|
+
#
|
178
|
+
# For example, it can be used to define a common guard:
|
179
|
+
#
|
180
|
+
# with if: -> { ... } do
|
181
|
+
# param :foo, type: :string
|
182
|
+
# param :bar, type: :string
|
183
|
+
# param :baz, type: :hash do
|
184
|
+
# param :qux, type: :string
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
# In this example, :foo, :bar, and :baz will inherit the if: guard,
|
189
|
+
# but :qux will not, since it is not a direct child.
|
190
|
+
#
|
191
|
+
def with(**kwargs, &)
|
192
|
+
orig = @options
|
193
|
+
@options = kwargs
|
194
|
+
|
195
|
+
yield
|
196
|
+
|
197
|
+
@options = orig
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# param defines a keyed parameter for a hash schema.
|
202
|
+
def param(key, type:, **kwargs, &block)
|
203
|
+
raise NotImplementedError, "cannot define param for non-hash type (got #{self.type})" unless
|
204
|
+
Types.hash?(children)
|
205
|
+
|
206
|
+
raise ArgumentError, "key #{key} has already been defined" if
|
207
|
+
children.key?(key)
|
208
|
+
|
209
|
+
children[key] = Schema.new(**options, **kwargs, key:, type:, strict:, source:, casing:, parent: self, &block)
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
# params defines multiple like-parameters for a hash schema.
|
214
|
+
def params(*keys, **kwargs, &) = keys.each { param(_1, **kwargs, &) }
|
215
|
+
|
216
|
+
##
|
217
|
+
# item defines an indexed parameter for an array schema.
|
218
|
+
def item(key = children&.size || 0, type:, **kwargs, &block)
|
219
|
+
raise NotImplementedError, "cannot define item for non-array type (got #{self.type})" unless
|
220
|
+
Types.array?(children)
|
221
|
+
|
222
|
+
raise ArgumentError, "index #{key} has already been defined" if
|
223
|
+
children[key].present? || boundless?
|
224
|
+
|
225
|
+
children << Schema.new(**options, **kwargs, key:, type:, strict:, source:, casing:, parent: self, &block)
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# items defines a set of like-parameters for an array schema.
|
230
|
+
def items(**kwargs, &)
|
231
|
+
item(0, **kwargs, &)
|
232
|
+
|
233
|
+
boundless!
|
234
|
+
end
|
235
|
+
|
236
|
+
def path
|
237
|
+
key = @key == ROOT ? nil : @key
|
238
|
+
|
239
|
+
@path ||= Path.new(*parent&.path&.keys, *key)
|
240
|
+
end
|
241
|
+
|
242
|
+
def keys
|
243
|
+
return [] if
|
244
|
+
children.blank?
|
245
|
+
|
246
|
+
case children
|
247
|
+
when Array
|
248
|
+
(0...children.size).to_a
|
249
|
+
when Hash
|
250
|
+
children.keys
|
251
|
+
else
|
252
|
+
[]
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def root? = key == ROOT
|
257
|
+
def child? = !root?
|
258
|
+
def children? = !children.blank?
|
259
|
+
def strict? = !!strict
|
260
|
+
def lenient? = !strict?
|
261
|
+
def optional? = !!@optional
|
262
|
+
def required? = !optional?
|
263
|
+
def coerce? = !!@coerce
|
264
|
+
def allow_blank? = !!@allow_blank
|
265
|
+
def allow_nil? = !!@allow_nil
|
266
|
+
def allow_non_scalars? = !!@allow_non_scalars
|
267
|
+
def nilify_blanks? = !!@nilify_blanks
|
268
|
+
def boundless? = !!@boundless
|
269
|
+
def indexed? = !boundless?
|
270
|
+
def if? = !@if.nil?
|
271
|
+
def unless? = !@unless.nil?
|
272
|
+
def array? = Types.array?(type)
|
273
|
+
def hash? = Types.hash?(type)
|
274
|
+
def scalar? = Types.scalar?(type)
|
275
|
+
def formatter? = !!@formatter
|
276
|
+
|
277
|
+
def inspect
|
278
|
+
"#<#{self.class.name} key=#{key.inspect} type=#{type.inspect} children=#{children.inspect}>"
|
279
|
+
end
|
280
|
+
|
281
|
+
private
|
282
|
+
|
283
|
+
attr_reader :controller,
|
284
|
+
:options,
|
285
|
+
:strict,
|
286
|
+
:casing
|
287
|
+
|
288
|
+
def boundless! = @boundless = true
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'typed_params/mapper'
|
4
|
+
|
5
|
+
module TypedParams
|
6
|
+
class Transformer < Mapper
|
7
|
+
def call(params)
|
8
|
+
depth_first_map(params) do |param|
|
9
|
+
schema = param.schema
|
10
|
+
parent = param.parent
|
11
|
+
|
12
|
+
# Ignore nil optionals when config is enabled
|
13
|
+
unless schema.allow_nil?
|
14
|
+
if param.value.nil? && schema.optional? && TypedParams.config.ignore_nil_optionals
|
15
|
+
param.delete
|
16
|
+
|
17
|
+
break
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
schema.transforms.map do |transform|
|
22
|
+
key, value = transform.call(param.key, param.value)
|
23
|
+
if key.nil?
|
24
|
+
param.delete
|
25
|
+
|
26
|
+
break
|
27
|
+
end
|
28
|
+
|
29
|
+
# Check for nils again after transform
|
30
|
+
unless schema.allow_nil?
|
31
|
+
if value.nil? && schema.optional? && TypedParams.config.ignore_nil_optionals
|
32
|
+
param.delete
|
33
|
+
|
34
|
+
break
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# If param's key has changed, we want to rename the key
|
39
|
+
# for its parent too.
|
40
|
+
if param.parent? && param.key != key
|
41
|
+
parent[key] = param.delete
|
42
|
+
end
|
43
|
+
|
44
|
+
param.key, param.value = key, value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'typed_params/transforms/transform'
|
4
|
+
|
5
|
+
module TypedParams
|
6
|
+
module Transforms
|
7
|
+
class KeyAlias < Transform
|
8
|
+
def initialize(as) = @as = as
|
9
|
+
def call(_, value) = [as, value]
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :as
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'typed_params/transforms/transform'
|
4
|
+
|
5
|
+
module TypedParams
|
6
|
+
module Transforms
|
7
|
+
class KeyCasing < Transform
|
8
|
+
def initialize(casing) = @casing = casing
|
9
|
+
|
10
|
+
def call(key, value)
|
11
|
+
transformed_key = transform_key(key)
|
12
|
+
transformed_value = transform_value(value)
|
13
|
+
|
14
|
+
[transformed_key, transformed_value]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :casing
|
20
|
+
|
21
|
+
def transform_string(str)
|
22
|
+
case casing
|
23
|
+
when :underscore
|
24
|
+
str.underscore
|
25
|
+
when :camel
|
26
|
+
str.underscore.camelize
|
27
|
+
when :lower_camel
|
28
|
+
str.underscore.camelize(:lower)
|
29
|
+
when :dash
|
30
|
+
str.underscore.dasherize
|
31
|
+
else
|
32
|
+
str
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def transform_key(key)
|
37
|
+
case key
|
38
|
+
when String
|
39
|
+
transform_string(key)
|
40
|
+
when Symbol
|
41
|
+
transform_string(key.to_s).to_sym
|
42
|
+
else
|
43
|
+
key
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def transform_value(value)
|
48
|
+
case value
|
49
|
+
when Hash
|
50
|
+
value.deep_transform_keys { transform_key(_1) }
|
51
|
+
when Array
|
52
|
+
value.map { transform_value(_1) }
|
53
|
+
else
|
54
|
+
value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|