sinatra-my-params 0.0.10 → 0.0.12

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: e8b53c81f8e6331c9f40d1b36cd45393132c6345478ae80132def61c44f5e6bd
4
- data.tar.gz: d049d0ec154d84196d13ac5db1ffe7d07ca8926e8c9a8e84f1ae0d7707b9a79c
3
+ metadata.gz: 3559e8affd6284a7ae9c07239ce1e4709fdab501899e7fc272efdad7fd25a0f8
4
+ data.tar.gz: 5187c37f8fd9d090d5fccd1c9b954b44bdc284f63dae452b8a9c2810f57fdb9e
5
5
  SHA512:
6
- metadata.gz: c424f3ff45c18ada07d4e9377177793849c665ae69b6198ab1742c533b3c1dc647ff302b69e987e1d10caa87024787fa9197c940807e8c2367e54db9a31f1661
7
- data.tar.gz: 4ea6c14c51e42bc649ad6251503173a9765188f3281bd7c56e3cd7aa2bb8aa62dc2c0e24b2fb8560c0fe06ed6868ad60675b0ea34c3462bae4cb520823250309
6
+ metadata.gz: 7621f5ef6dfccb1ac643132708cc8705e4a78e7c52a1ea33efd33932c911d78a7dcb8d01f35426a722f956fe3f0b7e524be12745de7385f039431b4f2762835d
7
+ data.tar.gz: a5463391804fa066d6cde70582698de0e144aaa4cf39bc4ffa925bc7e3a11cc9a58381de74d87131afb3effaf0db60c489a91227703b389939b8542c16e64ab9
data/Rakefile CHANGED
@@ -1,13 +1,5 @@
1
- require "rake/testtask"
2
1
  require "rspec/core/rake_task"
3
2
 
4
- Rake::TestTask.new do |t|
5
- t.libs << "test"
6
- end
3
+ RSpec::Core::RakeTask.new(:spec)
7
4
 
8
- begin
9
- RSpec::Core::RakeTask.new(:spec)
10
-
11
- task :default => :spec
12
- rescue LoadError
13
- end
5
+ task :default => :spec
data/lib/permit_params.rb CHANGED
@@ -1,26 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'date'
4
+ require 'time'
5
+
3
6
  module PermitParams
4
7
  class InvalidParameterError < StandardError
5
8
  attr_accessor :param, :options
6
9
  end
7
10
 
11
+ # Raised when a field marked required: true has no value and no
12
+ # default:. Always a subclass of InvalidParameterError, so existing
13
+ # `rescue PermitParams::InvalidParameterError` code keeps working
14
+ # unchanged if you don't care about the distinction.
15
+ class MissingParameterError < InvalidParameterError; end
16
+
8
17
  def permitted_params(params, permitted = {}, strong_validation = false, options = {})
9
18
  return params if permitted.empty?
10
19
 
11
- coerced_params = Hash.new({})
20
+ coerced_params = {}
21
+ seen = {}
12
22
 
13
23
  params.each do |key, value|
14
24
  next unless permitted?(permitted: permitted, key: key, value: value)
15
25
 
26
+ spec = normalize_spec(permitted[key.to_sym])
27
+ seen[key.to_sym] = true
28
+
16
29
  coerced = coerce(
17
30
  param: value,
18
- type: permitted[key.to_sym],
31
+ type: spec[:type],
19
32
  strong_validation: strong_validation,
20
- options: options
33
+ options: merge_field_options(options, spec)
21
34
  )
22
- coerced_params[key] = coerced unless coerced.nil?
35
+ coerced = apply_constraints(coerced, spec, raw_value: value, strong_validation: strong_validation)
36
+
37
+ if coerced.nil?
38
+ fill_missing!(coerced_params, key, spec, present: true, raw_value: value)
39
+ else
40
+ coerced_params[key] = coerced
41
+ end
42
+ end
43
+
44
+ permitted.each_key do |perm_key|
45
+ next if seen[perm_key]
46
+
47
+ spec = normalize_spec(permitted[perm_key])
48
+ fill_missing!(coerced_params, perm_key, spec, present: false, raw_value: nil)
23
49
  end
50
+
24
51
  coerced_params
25
52
  end
26
53
 
@@ -30,34 +57,140 @@ module PermitParams
30
57
  Any = :any
31
58
  Shape = :shape
32
59
 
60
+ # Mirrors the fix Ruby itself shipped for CVE-2021-41817 (ReDoS in
61
+ # Date/Time/DateTime parsing methods): never hand an unbounded,
62
+ # attacker-controlled string to a parser that uses backtracking regexes.
63
+ PARSE_LENGTH_LIMIT = 128
64
+
65
+ DATE_TIME_TYPES = [Date, Time, DateTime].freeze
66
+ NUMERIC_PARSE_TYPES = [Integer, Float].freeze
67
+
68
+ # Per-field overrides that can live inside a Hash spec (e.g.
69
+ # `{ type: Array, of: Integer, delimiter: ';' }`) instead of only in
70
+ # the global 4th-argument `options` hash. Field-level values win.
71
+ FIELD_OPTION_KEYS = %i[delimiter separator integer_precision shape of].freeze
72
+
33
73
  def permitted?(permitted:, key:, value:)
34
74
  permitted.keys.map(&:to_s).include?(key.to_s) && !value.nil?
35
75
  end
36
76
 
77
+ # A permitted value can still just be a bare type (`Integer`, `String`,
78
+ # `Boolean`, ...) exactly like before - that keeps every pre-0.0.12
79
+ # usage working unchanged. It can now ALSO be a Hash describing a
80
+ # richer contract: { type:, required:, default:, in:, match:, min:,
81
+ # max:, of:, shape:, delimiter:, separator:, integer_precision: }.
82
+ def normalize_spec(raw_spec)
83
+ if raw_spec.is_a?(::Hash)
84
+ spec = raw_spec.dup
85
+ spec[:type] = Any unless spec.key?(:type)
86
+ spec
87
+ else
88
+ { type: raw_spec }
89
+ end
90
+ end
91
+
92
+ def merge_field_options(global_options, spec)
93
+ overrides = spec.select { |k, _| FIELD_OPTION_KEYS.include?(k) }
94
+ global_options.merge(overrides)
95
+ end
96
+
97
+ # Called once per permitted field whenever it ends up without a
98
+ # coerced value - either because it failed validation (present: true)
99
+ # or because it was never supplied at all (present: false).
100
+ def fill_missing!(coerced_params, key, spec, present:, raw_value:)
101
+ if spec.key?(:default)
102
+ coerced_params[key] = spec[:default]
103
+ elsif spec[:required]
104
+ if present
105
+ raise InvalidParameterError, "'#{raw_value}' is not a valid #{spec[:type]}"
106
+ else
107
+ raise MissingParameterError, "'#{key}' is required"
108
+ end
109
+ end
110
+ # else: optional and absent/invalid - silently omitted, same as pre-0.0.12
111
+ end
112
+
113
+ def apply_constraints(value, spec, raw_value:, strong_validation:)
114
+ return value if value.nil?
115
+
116
+ violation = constraint_violation(value, spec)
117
+ return value unless violation
118
+
119
+ raise InvalidParameterError, "'#{raw_value}' is not a valid #{spec[:type]} (#{violation})" if strong_validation
120
+
121
+ nil
122
+ end
123
+
124
+ def constraint_violation(value, spec)
125
+ return "must be one of #{spec[:in].inspect}" if spec[:in] && !spec[:in].include?(value)
126
+ return 'does not match required format' if spec[:match] && !(value.is_a?(String) && spec[:match].match?(value))
127
+
128
+ comparable = comparable_value(value)
129
+ return "must be >= #{spec[:min]}" if spec[:min] && comparable && comparable < spec[:min]
130
+ return "must be <= #{spec[:max]}" if spec[:max] && comparable && comparable > spec[:max]
131
+
132
+ nil
133
+ end
134
+
135
+ # What min:/max: compares against: the value itself for numbers, its
136
+ # length for anything else that has one (String, Array, Hash).
137
+ def comparable_value(value)
138
+ return value if value.is_a?(Numeric)
139
+ return value.length if value.respond_to?(:length)
140
+
141
+ nil
142
+ end
143
+
37
144
  def coerce(param:, type:, strong_validation: false, options: {})
38
145
  return param if type == Any
39
146
 
40
147
  begin
41
148
  return nil if param.nil?
42
- return param if begin
149
+
150
+ # `of:` on an Array field means every element needs its own
151
+ # coercion, so an already-Array param (e.g. from a JSON body)
152
+ # can't take the "already the right class" shortcut below.
153
+ of = options[:of]
154
+ array_with_of = type == Array && of
155
+
156
+ return param if !array_with_of && begin
43
157
  param.is_a?(type)
44
158
  rescue StandardError
45
159
  false
46
160
  end
161
+
162
+ if param.is_a?(String) && (DATE_TIME_TYPES.include?(type) || NUMERIC_PARSE_TYPES.include?(type))
163
+ raise ArgumentError, "'#{type}' input exceeds #{PARSE_LENGTH_LIMIT} bytes" if param.bytesize > PARSE_LENGTH_LIMIT
164
+ end
165
+
47
166
  return coerce_integer(param, options) if type == Integer
48
167
  return Float(param) if type == Float
49
168
  return String(param) if type == String
50
169
  return Date.parse(param) if type == Date
51
170
  return Time.parse(param) if type == Time
52
171
  return DateTime.parse(param) if type == DateTime
53
- return coerce_array(param, options) if type == Array
172
+ return coerce_array(param, options, strong_validation: strong_validation) if type == Array
54
173
  return coerce_shape(param, options) if type == Shape
55
174
  return coerce_hash(param, options) if type == Hash
56
175
  return coerce_boolean(param) if [TrueClass, FalseClass, Boolean].include? type
57
176
 
58
177
  nil
59
- rescue ArgumentError
178
+ rescue InvalidParameterError
179
+ # Raised by a nested coerce() call (e.g. one bad element inside an
180
+ # `of:` array). Re-raise as-is so the precise inner message survives
181
+ # instead of being replaced by a generic outer one.
182
+ raise if strong_validation
183
+
184
+ nil
185
+ rescue StandardError
186
+ # Any other failure while coercing untrusted input (malformed
187
+ # value, unexpected nested shape, missing stdlib constant, etc.)
188
+ # must never crash the caller's request handler - it's either
189
+ # rejected loudly (strong_validation) or dropped silently, same
190
+ # as an invalid value.
60
191
  raise InvalidParameterError, "'#{param}' is not a valid #{type}" if strong_validation
192
+
193
+ nil
61
194
  end
62
195
  end
63
196
 
@@ -65,11 +198,21 @@ module PermitParams
65
198
  Integer(param, options[:integer_precision] || 10)
66
199
  end
67
200
 
68
- def coerce_array(param, options = {})
69
- delimiter = valid_delimiter?(param, options[:delimiter])
70
- return unless delimiter
201
+ def coerce_array(param, options = {}, strong_validation: false)
202
+ elements =
203
+ if param.is_a?(Array)
204
+ param
205
+ else
206
+ delimiter = valid_delimiter?(param, options[:delimiter])
207
+ return unless delimiter
208
+
209
+ param.split(delimiter).map(&:strip)
210
+ end
71
211
 
72
- Array(param.split(delimiter).map(&:strip))
212
+ of = options[:of]
213
+ return Array(elements) unless of
214
+
215
+ elements.map { |el| coerce(param: el, type: of, strong_validation: strong_validation, options: options) }.compact
73
216
  end
74
217
 
75
218
  def coerce_hash(param, options = {})
@@ -88,10 +231,13 @@ module PermitParams
88
231
  end
89
232
 
90
233
  def coerce_boolean(param)
91
- coerced = if /^(false|f|no|n|0)$/i === param.to_s
234
+ # \A/\z (not ^/$) anchor to the start/end of the *whole string*. In Ruby
235
+ # ^ and $ only anchor to line boundaries, so e.g. "true\nDROP TABLE..."
236
+ # would incorrectly match as a clean boolean with ^/$.
237
+ coerced = if /\A(false|f|no|n|0)\z/i === param.to_s
92
238
  false
93
239
  else
94
- /^(true|t|yes|y|1)$/i === param.to_s ? true : nil
240
+ /\A(true|t|yes|y|1)\z/i === param.to_s ? true : nil
95
241
  end
96
242
  raise ArgumentError if coerced.nil?
97
243
 
@@ -109,11 +255,18 @@ module PermitParams
109
255
  end
110
256
 
111
257
  def coerce_shape(param, options = {})
112
- hash = coerce_hash(param)
258
+ hash = coerce_hash(param, options)
113
259
  has_shape?(hash, options[:shape]) ? hash : nil
114
260
  end
115
261
 
116
262
  def has_shape?(hash, shape)
263
+ # `hash` can be nil (coerce_hash gave up on a malformed string) and
264
+ # `shape` can be nil/non-Hash (caller forgot to pass shape:, or an
265
+ # attacker sent an unexpected nested hash the shape never described).
266
+ # Either used to raise NoMethodError deep inside a supposedly "safe"
267
+ # input filter, crashing the whole request. Reject instead of raising.
268
+ return false unless hash.is_a?(Hash) && shape.is_a?(Hash)
269
+
117
270
  hash.all? do |k, v|
118
271
  v.is_a?(Hash) ? has_shape?(v, shape[k]) : shape[k] === v
119
272
  end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'permit_params'
4
+ require 'rspec'
5
+
6
+ describe PermitParams do
7
+ include PermitParams
8
+
9
+ # -- required: ------------------------------------------------------
10
+
11
+ describe 'required:' do
12
+ it 'raises MissingParameterError when a required param is absent' do
13
+ expect do
14
+ permitted_params({}, { email: { type: String, required: true } })
15
+ end.to raise_error(PermitParams::MissingParameterError, "'email' is required")
16
+ end
17
+
18
+ it 'raises MissingParameterError when a required param is explicitly nil' do
19
+ expect do
20
+ permitted_params({ email: nil }, { email: { type: String, required: true } })
21
+ end.to raise_error(PermitParams::MissingParameterError, "'email' is required")
22
+ end
23
+
24
+ it 'raises InvalidParameterError (not MissingParameterError) when present but invalid, even with strong_validation false' do
25
+ expect do
26
+ permitted_params({ age: 'not a number' }, { age: { type: Integer, required: true } })
27
+ end.to raise_error(PermitParams::InvalidParameterError, "'not a number' is not a valid Integer")
28
+ end
29
+
30
+ it 'does not raise when a required param is present and valid' do
31
+ output = permitted_params({ email: 'a@b.com' }, { email: { type: String, required: true } })
32
+ expect(output).to eq(email: 'a@b.com')
33
+ end
34
+
35
+ it 'is a no-op for optional params left unspecified (backward compatible default)' do
36
+ output = permitted_params({}, { nickname: String })
37
+ expect(output).to eq({})
38
+ end
39
+
40
+ it 'MissingParameterError is also an InvalidParameterError, so old rescue clauses still catch it' do
41
+ expect(PermitParams::MissingParameterError.ancestors).to include(PermitParams::InvalidParameterError)
42
+ end
43
+ end
44
+
45
+ # -- default: ---------------------------------------------------------
46
+
47
+ describe 'default:' do
48
+ it 'fills in the default when the param is absent' do
49
+ output = permitted_params({}, { role: { type: String, default: 'member' } })
50
+ expect(output).to eq(role: 'member')
51
+ end
52
+
53
+ it 'fills in the default when the param fails coercion' do
54
+ output = permitted_params({ limit: 'lots' }, { limit: { type: Integer, default: 10 } })
55
+ expect(output).to eq(limit: 10)
56
+ end
57
+
58
+ it 'uses the given value instead of the default when valid' do
59
+ output = permitted_params({ limit: '25' }, { limit: { type: Integer, default: 10 } })
60
+ expect(output).to eq(limit: 25)
61
+ end
62
+
63
+ it 'default takes priority over required (no error raised)' do
64
+ output = permitted_params({}, { role: { type: String, required: true, default: 'member' } })
65
+ expect(output).to eq(role: 'member')
66
+ end
67
+ end
68
+
69
+ # -- in: (enum) ---------------------------------------------------------
70
+
71
+ describe 'in:' do
72
+ let(:permitted) { { status: { type: String, in: %w[draft published] } } }
73
+
74
+ it 'keeps a value that is in the allowed list' do
75
+ output = permitted_params({ status: 'draft' }, permitted)
76
+ expect(output).to eq(status: 'draft')
77
+ end
78
+
79
+ it 'drops a value that is not in the allowed list (lenient)' do
80
+ output = permitted_params({ status: 'deleted' }, permitted)
81
+ expect(output).to eq({})
82
+ end
83
+
84
+ it 'raises with strong_validation when value is not in the allowed list' do
85
+ expect do
86
+ permitted_params({ status: 'deleted' }, permitted, true)
87
+ end.to raise_error(PermitParams::InvalidParameterError, "'deleted' is not a valid String (must be one of [\"draft\", \"published\"])")
88
+ end
89
+ end
90
+
91
+ # -- match: (regex) ------------------------------------------------------
92
+
93
+ describe 'match:' do
94
+ let(:permitted) { { code: { type: String, match: /\A[A-Z]{3}\d{3}\z/ } } }
95
+
96
+ it 'keeps a value that matches the format' do
97
+ output = permitted_params({ code: 'ABC123' }, permitted)
98
+ expect(output).to eq(code: 'ABC123')
99
+ end
100
+
101
+ it 'drops a value that does not match the format (lenient)' do
102
+ output = permitted_params({ code: 'nope' }, permitted)
103
+ expect(output).to eq({})
104
+ end
105
+
106
+ it 'raises with strong_validation when value does not match the format' do
107
+ expect do
108
+ permitted_params({ code: 'nope' }, permitted, true)
109
+ end.to raise_error(PermitParams::InvalidParameterError)
110
+ end
111
+ end
112
+
113
+ # -- min: / max: ----------------------------------------------------------
114
+
115
+ describe 'min: / max:' do
116
+ it 'keeps a numeric value within bounds' do
117
+ output = permitted_params({ age: '25' }, { age: { type: Integer, min: 18, max: 65 } })
118
+ expect(output).to eq(age: 25)
119
+ end
120
+
121
+ it 'drops a numeric value below the minimum' do
122
+ output = permitted_params({ age: '10' }, { age: { type: Integer, min: 18 } })
123
+ expect(output).to eq({})
124
+ end
125
+
126
+ it 'drops a numeric value above the maximum' do
127
+ output = permitted_params({ age: '99' }, { age: { type: Integer, max: 65 } })
128
+ expect(output).to eq({})
129
+ end
130
+
131
+ it 'applies min:/max: to string length' do
132
+ output = permitted_params({ name: 'a' }, { name: { type: String, min: 2 } })
133
+ expect(output).to eq({})
134
+ end
135
+
136
+ it 'applies min:/max: to array length' do
137
+ output = permitted_params({ tags: %w[a b] }, { tags: { type: Array, max: 1 } })
138
+ expect(output).to eq({})
139
+ end
140
+ end
141
+
142
+ # -- of: (typed array elements) --------------------------------------------
143
+
144
+ describe 'of:' do
145
+ it 'coerces each delimited element to the given type' do
146
+ output = permitted_params({ ids: '1,2,3' }, { ids: { type: Array, of: Integer } })
147
+ expect(output).to eq(ids: [1, 2, 3])
148
+ end
149
+
150
+ it 'coerces each element of an already-Array param (e.g. JSON body)' do
151
+ output = permitted_params({ ids: %w[1 2 3] }, { ids: { type: Array, of: Integer } })
152
+ expect(output).to eq(ids: [1, 2, 3])
153
+ end
154
+
155
+ it 'drops individual invalid elements (lenient)' do
156
+ output = permitted_params({ ids: '1,x,3' }, { ids: { type: Array, of: Integer } })
157
+ expect(output).to eq(ids: [1, 3])
158
+ end
159
+
160
+ it 'raises with strong_validation, preserving the precise inner element message' do
161
+ expect do
162
+ permitted_params({ ids: '1,x,3' }, { ids: { type: Array, of: Integer } }, true)
163
+ end.to raise_error(PermitParams::InvalidParameterError, "'x' is not a valid Integer")
164
+ end
165
+
166
+ it 'supports of: Shape for an array of nested objects' do
167
+ permitted = { items: { type: Array, of: PermitParams::Shape, shape: { id: Integer, name: String } } }
168
+ input = { items: [{ id: 1, name: 'a' }, { id: 2, name: 'b' }] }
169
+ output = permitted_params(input, permitted)
170
+ expect(output).to eq(items: [{ id: 1, name: 'a' }, { id: 2, name: 'b' }])
171
+ end
172
+
173
+ it 'drops array elements whose shape does not match' do
174
+ permitted = { items: { type: Array, of: PermitParams::Shape, shape: { id: Integer } } }
175
+ input = { items: [{ id: 1 }, { id: 'not an integer' }] }
176
+ output = permitted_params(input, permitted)
177
+ expect(output).to eq(items: [{ id: 1 }])
178
+ end
179
+ end
180
+
181
+ # -- per-field shape:/delimiter:/separator:/integer_precision: -------------
182
+
183
+ describe 'per-field options override the global options hash' do
184
+ it 'lets two different Shape params use two different shapes at once' do
185
+ permitted = {
186
+ author: { type: PermitParams::Shape, shape: { name: String } },
187
+ book: { type: PermitParams::Shape, shape: { title: String, year: Integer } }
188
+ }
189
+ input = { author: { name: 'Ada' }, book: { title: 'Notes', year: 1843 } }
190
+
191
+ output = permitted_params(input, permitted)
192
+ expect(output).to eq(author: { name: 'Ada' }, book: { title: 'Notes', year: 1843 })
193
+ end
194
+
195
+ it 'falls back to the global options hash shape: when a field does not override it' do
196
+ permitted = { legacy: PermitParams::Shape }
197
+ output = permitted_params({ legacy: { a: 1 } }, permitted, false, shape: { a: Integer })
198
+ expect(output).to eq(legacy: { a: 1 })
199
+ end
200
+
201
+ it 'lets a field override the global delimiter' do
202
+ permitted = { tags: { type: Array, delimiter: ';' } }
203
+ output = permitted_params({ tags: '1; 2' }, permitted, false, delimiter: ',')
204
+ expect(output).to eq(tags: %w[1 2])
205
+ end
206
+ end
207
+
208
+ # -- combinations -----------------------------------------------------------
209
+
210
+ describe 'combined real-world example' do
211
+ it 'validates a small "create user" style payload in one call' do
212
+ permitted = {
213
+ email: { type: String, required: true, match: /\A[^@\s]+@[^@\s]+\z/ },
214
+ role: { type: String, in: %w[member admin], default: 'member' },
215
+ age: { type: Integer, min: 13 },
216
+ tags: { type: Array, of: String, max: 5 }
217
+ }
218
+
219
+ output = permitted_params(
220
+ { email: 'a@b.com', age: '30', tags: 'ruby,sinatra' },
221
+ permitted
222
+ )
223
+
224
+ expect(output).to eq(
225
+ email: 'a@b.com',
226
+ role: 'member',
227
+ age: 30,
228
+ tags: %w[ruby sinatra]
229
+ )
230
+ end
231
+ end
232
+ end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'test/unit'
4
3
  require 'permit_params'
5
4
  require 'rspec'
6
5
  require 'rack/test'
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'test/unit'
4
3
  require 'permit_params'
5
4
  require 'rspec'
6
5
  require 'rack/test'
metadata CHANGED
@@ -1,15 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-my-params
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Aviles
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-19 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2026-08-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack-test
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.12'
13
55
  description: A simple params sanitizer (originally created for sinatra)
14
56
  email: gdmarav374@gmail.com
15
57
  executables: []
@@ -19,6 +61,7 @@ files:
19
61
  - Rakefile
20
62
  - bin/permit_params
21
63
  - lib/permit_params.rb
64
+ - spec/permit_params_advanced_spec.rb
22
65
  - spec/permit_params_shape_spec.rb
23
66
  - spec/permit_params_spec.rb
24
67
  homepage: https://github.com/mhero/sinatra-my-params
@@ -32,17 +75,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
32
75
  requirements:
33
76
  - - ">="
34
77
  - !ruby/object:Gem::Version
35
- version: '0'
78
+ version: '2.6'
36
79
  required_rubygems_version: !ruby/object:Gem::Requirement
37
80
  requirements:
38
81
  - - ">="
39
82
  - !ruby/object:Gem::Version
40
83
  version: '0'
41
84
  requirements: []
42
- rubygems_version: 3.2.3
85
+ rubygems_version: 3.4.19
43
86
  signing_key:
44
87
  specification_version: 3
45
88
  summary: permit_params!
46
89
  test_files:
47
90
  - spec/permit_params_spec.rb
48
91
  - spec/permit_params_shape_spec.rb
92
+ - spec/permit_params_advanced_spec.rb