• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MushroomObserver / mushroom-observer / 13128746925

04 Feb 2025 05:06AM UTC coverage: 93.302% (-0.02%) from 93.318%
13128746925

Pull #2696

github

web-flow
Merge ede9b5d49 into f7b18aae2
Pull Request #2696: Remove unused `missing_param` checks in Query::Validation

53 of 61 new or added lines in 2 files covered. (86.89%)

5 existing lines in 4 files now uncovered.

27372 of 29337 relevant lines covered (93.3%)

571.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.97
/app/classes/query/modules/validation.rb
1
# frozen_string_literal: true
2

3
# validation of Query parameters
4
module Query::Modules::Validation # rubocop:disable Metrics/ModuleLength
1✔
5
  attr_accessor :params, :params_cache
1✔
6

7
  def validate_params
1✔
8
    old_params = @params.dup.compact.symbolize_keys
2,919✔
9
    new_params = {}
2,919✔
10
    permitted_params = parameter_declarations.slice(*old_params.keys)
2,919✔
11
    permitted_params.each do |param, param_type|
2,919✔
12
      val = old_params[param]
5,012✔
13
      val = validate_value(param_type, param, val) if val.present?
5,012✔
14
      new_params[param] = val
4,995✔
15
    end
16
    check_for_unexpected_params(old_params)
2,902✔
17
    @params = new_params
2,901✔
18
  end
19

20
  def check_for_unexpected_params(old_params)
1✔
21
    unexpected_params = old_params.except(*parameter_declarations.keys)
2,902✔
22
    return if unexpected_params.keys.empty?
2,902✔
23

24
    str = unexpected_params.keys.map(&:to_s).join("', '")
1✔
25
    raise("Unexpected parameter(s) '#{str}' for #{model} query.")
1✔
26
  end
27

28
  def validate_value(param_type, param, val)
1✔
29
    if param_type.is_a?(Array)
4,904✔
30
      array_validate(param, val, param_type.first).flatten
1,543✔
31
    else
32
      # scalar_validate with ambiguous lookup could return an array
33
      val = scalar_validate(param, val, param_type)
3,361✔
34
      val = val.first if val.is_a?(Array)
3,349✔
35
      val
3,349✔
36
    end
37
  end
38

39
  def array_validate(param, val, param_type)
1✔
40
    case val
1,543✔
41
    when Array
42
      # scalar_validate with lookup could return multiple matches per val
43
      # so this could return a nested array.
44
      val[0, MO.query_max_array].map do |val2|
464✔
45
        scalar_validate(param, val2, param_type)
721✔
46
      end
47
    when ::API2::OrderedRange
48
      [scalar_validate(param, val.begin, param_type),
39✔
49
       scalar_validate(param, val.end, param_type)]
50
    else
51
      [scalar_validate(param, val, param_type)]
1,040✔
52
    end
53
  end
54

55
  def scalar_validate(param, val, param_type)
1✔
56
    case param_type
5,369✔
57
    when Symbol
58
      send(:"validate_#{param_type}", param, val)
3,433✔
59
    when Class
60
      validate_class_param(param, val, param_type)
1,770✔
61
    when Hash
62
      validate_hash_param(param, val, param_type)
166✔
63
    else
64
      raise("Invalid declaration of :#{param} for #{model} " \
×
65
            "query! (invalid type: #{param_type.class.name})")
66
    end
67
  end
68

69
  def validate_class_param(param, val, param_type)
1✔
70
    if param_type.respond_to?(:descends_from_active_record?)
1,770✔
71
      validate_record_or_id_or_string(param, val, param_type)
1,770✔
72
    else
NEW
73
      validate_poro(param, val, param_type)
×
74
    end
75
  end
76

77
  def validate_hash_param(param, val, param_type)
1✔
78
    if [:string, :boolean].include?(param_type.keys.first)
166✔
79
      validate_enum(param, val, param_type)
165✔
80
    else
81
      validate_nested_params(param, val, param_type)
1✔
82
    end
83
  end
84

85
  def validate_nested_params(_param, val, hash)
1✔
86
    val2 = {}
1✔
87
    hash.each do |key, arg_type|
1✔
88
      val2[key] = scalar_validate(key, val[key], arg_type)
4✔
89
    end
90
    val2
1✔
91
  end
92

93
  def validate_poro(param, val, param_type)
1✔
NEW
94
    unless defined?(param_type)
×
NEW
95
      raise(
×
96
        "Don't know how to parse #{param_type} :#{param} for #{model} query."
97
      )
98
    end
99

NEW
100
    return val if val.valid?
×
101

NEW
102
    raise(
×
103
      "Invalid #{param_type} instance passed for :#{param} for " \
104
      "#{model} query."
105
    )
106
  end
107

108
  def validate_enum(param, val, hash)
1✔
109
    if hash.keys.length != 1
165✔
110
      raise(
×
111
        "Invalid enum declaration for :#{param} for #{model} " \
112
        "query! (wrong number of keys in hash)"
113
      )
114
    end
115

116
    arg_type = hash.keys.first
165✔
117
    set = hash.values.first
165✔
118
    unless set.is_a?(Array)
165✔
119
      raise(
×
120
        "Invalid enum declaration for :#{param} for #{model} " \
121
        "query! (expected value to be an array of allowed values)"
122
      )
123
    end
124

125
    val2 = scalar_validate(param, val, arg_type)
165✔
126
    if (arg_type == :string) && set.include?(val2.to_sym)
164✔
127
      val2 = val2.to_sym
89✔
128
    elsif set.exclude?(val2)
75✔
129
      raise("Value for :#{param} should be one of the following: " \
2✔
130
            "#{set.inspect}.")
131
    end
132
    val2
162✔
133
  end
134

135
  def validate_boolean(param, val)
1✔
136
    case val
1,090✔
137
    # Disable cop because we do mean to symbols with boolean names
138
    # rubocop:disable Lint/BooleanSymbol
139
    when :true, :yes, :on, "true", "yes", "on", "1", 1, true
140
      true
1,086✔
141
    when :false, :no, :off, "false", "no", "off", "0", 0, false, nil
142
      false
4✔
143
    # rubocop:enable Lint/BooleanSymbol
144
    else
145
      raise("Value for :#{param} should be boolean, got: #{val.inspect}")
×
146
    end
147
  end
148

149
  def validate_integer(param, val)
1✔
150
    if val.is_a?(Integer) || val.is_a?(String) && val.match(/^-?\d+$/)
×
151
      val.to_i
×
152
    elsif val.blank?
×
153
      nil
154
    else
155
      raise("Value for :#{param} should be an integer, got: #{val.inspect}")
×
156
    end
157
  end
158

159
  def validate_float(param, val)
1✔
160
    if val.is_a?(Integer) || val.is_a?(Float) ||
84✔
161
       (val.is_a?(String) && val.match(/^-?(\d+(\.\d+)?|\.\d+)$/))
5✔
162
      val.to_f
84✔
163
    else
164
      raise("Value for :#{param} should be a float, got: #{val.inspect}")
×
165
    end
166
  end
167

168
  def validate_record_or_id_or_string(param, val, type = ActiveRecord::Base)
1✔
169
    if val.is_a?(type)
1,770✔
170
      raise("Value for :#{param} is an unsaved #{type} instance.") unless val.id
335✔
171

172
      set_cached_parameter_instance(param, val)
335✔
173
      val.id
335✔
174
    elsif could_be_record_id?(param, val)
1,435✔
175
      val.to_i
1,332✔
176
    elsif val.is_a?(String) && param != :ids
103✔
177
      lookup_records_by_name(type, val, retrieve: :ids, first: false)
96✔
178
    else
179
      raise("Value for :#{param} should be id, string " \
7✔
180
            "or #{type} instance, got: #{val.inspect}")
181
    end
182
  end
183

184
  def validate_string(param, val)
1✔
185
    if val.is_any?(Integer, Float, String, Symbol)
2,055✔
186
      val.to_s
2,047✔
187
    else
188
      raise("Value for :#{param} should be a string or symbol, " \
8✔
189
            "got a #{val.class}: #{val.inspect}")
190
    end
191
  end
192

193
  def validate_id(param, val, type = ActiveRecord::Base)
1✔
194
    if val.is_a?(type)
×
195
      raise("Value for :#{param} is an unsaved #{type} instance.") unless val.id
×
196

NEW
197
      set_cached_parameter_instance(param, val)
×
UNCOV
198
      val.id
×
199
    elsif could_be_record_id?(param, val)
×
200
      val.to_i
×
201
    else
202
      raise("Value for :#{param} should be id or #{type} instance, " \
×
203
            "got: #{val.inspect}")
204
    end
205
  end
206

207
  def validate_name(param, val)
1✔
208
    case val
×
209
    when Name
210
      raise("Value for :#{param} is an unsaved Name instance.") unless val.id
×
211

NEW
212
      set_cached_parameter_instance(param, val)
×
213
      val.id
×
214
    when String, Integer
215
      val
×
216
    else
217
      raise("Value for :#{param} should be a Name, String or Integer, " \
×
218
            "got: #{val.class}")
219
    end
220
  end
221

222
  def validate_date(param, val)
1✔
223
    if val.acts_like?(:date)
55✔
224
      format("%04d-%02d-%02d", val.year, val.mon, val.day)
19✔
225
    elsif /^\d\d\d\d(-\d\d?){0,2}$/i.match?(val.to_s) ||
36✔
226
          /^\d\d?(-\d\d?)?$/i.match?(val.to_s)
227
      val
36✔
228
    elsif val.blank? || val.to_s == "0"
×
229
      nil
230
    else
231
      raise("Value for :#{param} should be a date (YYYY-MM-DD or MM-DD), " \
×
232
            "got: #{val.inspect}")
233
    end
234
  end
235

236
  def validate_time(param, val)
1✔
237
    if val.acts_like?(:time)
104✔
238
      val = val.utc
62✔
239
      format("%04d-%02d-%02d-%02d-%02d-%02d",
62✔
240
             val.year, val.mon, val.day, val.hour, val.min, val.sec)
241
    elsif /^\d\d\d\d(-\d\d?){0,5}$/i.match?(val.to_s)
42✔
242
      val
42✔
243
    elsif val.blank? || val.to_s == "0"
×
244
      nil
245
    else
246
      raise(
×
247
        "Value for :#{param} should be a UTC time (YYYY-MM-DD-HH-MM-SS), " \
248
        "got: #{val.class.name}::#{val.inspect}"
249
      )
250
    end
251
  end
252

253
  def validate_query(param, val)
1✔
254
    case val
45✔
255
    when Query::Base
256
      val.record.id
9✔
257
    when Integer
258
      val
36✔
259
    else
260
      raise(
×
261
        "Value for :#{param} should be a Query class, got: #{val.inspect}"
262
      )
263
    end
264
  end
265

266
  def find_cached_parameter_instance(model, param)
1✔
267
    val = if could_be_record_id?(param, params[param])
279✔
268
            model.find(params[param])
279✔
269
          else
NEW
270
            lookup_records_by_name(model, params[param])
×
271
          end
272
    set_cached_parameter_instance(param, val)
279✔
273
  end
274

275
  def get_cached_parameter_instance(param)
1✔
276
    @params_cache ||= {}
×
277
    @params_cache[param]
×
278
  end
279

280
  # Cache the instance for later use, in case we both instantiate and
281
  # execute query in the same action.
282
  def set_cached_parameter_instance(param, val)
1✔
283
    @params_cache ||= {}
614✔
284
    @params_cache[param] = val
614✔
285
  end
286

287
  def could_be_record_id?(param, val)
1✔
288
    val.is_a?(Integer) ||
1,714✔
289
      val.is_a?(String) && val.match(/^[1-9]\d*$/) ||
290
      # (blasted admin user has id = 0!)
291
      val.is_a?(String) && (val == "0") && (param == :user)
99✔
292
  end
293

294
  def lookup_records_by_name(type, val, retrieve: :instances, first: true)
1✔
295
    lookup = "Lookup::#{type.name.pluralize}".constantize
96✔
296
    raise("#{lookup} not defined for : #{val.inspect}") unless defined?(lookup)
96✔
297

298
    # possible name lookup params
299
    # names_params = *names_parameter_declarations.except(:names).keys
300
    # lookup_params = params.slice(names_params).compact
301
    # results = lookup.new(val, lookup_params).send(retrieve)
302
    results = lookup.new(val).send(retrieve)
96✔
303
    raise("Couldn't find an id for : #{val.inspect}") unless results
96✔
304

305
    if first || results.size == 1
96✔
306
      results.first
89✔
307
    else
308
      results
7✔
309
    end
310
  end
311
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc