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

MushroomObserver / mushroom-observer / 13070378680

31 Jan 2025 09:55AM UTC coverage: 88.867% (-0.04%) from 88.911%
13070378680

push

github

nimmolo
Merge branch 'main' into query-scopes-folder

269 of 328 new or added lines in 21 files covered. (82.01%)

9 existing lines in 3 files now uncovered.

27355 of 30782 relevant lines covered (88.87%)

564.23 hits per line

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

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

3
# validation of Query parameters
4
module Query::Modules::Validation
1✔
5
  attr_accessor :params, :params_cache
1✔
6

7
  def validate_params
1✔
8
    old_params = @params.dup
2,906✔
9
    new_params = {}
2,906✔
10
    parameter_declarations.each do |param, param_type|
2,906✔
11
      validate_param(old_params, new_params, param, param_type)
127,969✔
12
    end
13
    check_for_unexpected_params(old_params)
2,889✔
14
    @params = new_params
2,888✔
15
  end
16

17
  def validate_param(old_params, new_params, param_sym, param_type)
1✔
18
    param = param_sym.to_s.sub(/\?$/, "").to_sym
127,969✔
19
    optional = true
127,969✔
20
    begin
21
      val = pop_param_value(old_params, param)
127,969✔
22
      val = validate_value(param_type, param, val) if val.present?
5,025✔
23
      if !val.nil?
5,008✔
24
        new_params[param] = val
4,987✔
25
      elsif !optional
21✔
26
        raise(
×
27
          "Missing :#{param} parameter for #{model} query."
28
        )
29
      else
30
        new_params[param] = nil
21✔
31
      end
32
    rescue MissingValue
33
      unless optional
122,944✔
34
        raise(
×
35
          "Missing :#{param} parameter for #{model} query."
36
        )
37
      end
38
    end
39
  end
40

41
  class MissingValue < RuntimeError; end
1✔
42

43
  def pop_param_value(old_params, param)
1✔
44
    if old_params.key?(param)
127,969✔
45
      val = old_params[param]
5,025✔
46
    elsif old_params.key?(param.to_s)
122,944✔
NEW
47
      val = old_params[param.to_s]
×
48
    else
49
      raise(MissingValue.new)
122,944✔
50
    end
51
    old_params.delete(param)
5,025✔
52
    old_params.delete(param.to_s)
5,025✔
53
    val
5,025✔
54
  end
55

56
  def check_for_unexpected_params(old_params)
1✔
57
    return if old_params.keys.empty?
2,889✔
58

59
    str = old_params.keys.map(&:to_s).join("', '")
1✔
60
    raise("Unexpected parameter(s) '#{str}' for #{model} query.")
1✔
61
  end
62

63
  def array_validate(param, val, param_type)
1✔
64
    case val
1,539✔
65
    when Array
66
      val[0, MO.query_max_array].map do |val2|
454✔
67
        scalar_validate(param, val2, param_type)
701✔
68
      end
69
    when ::API2::OrderedRange
70
      [scalar_validate(param, val.begin, param_type),
39✔
71
       scalar_validate(param, val.end, param_type)]
72
    else
73
      [scalar_validate(param, val, param_type)]
1,046✔
74
    end
75
  end
76

77
  def scalar_validate(param, val, param_type)
1✔
78
    if param_type.is_a?(Symbol)
5,346✔
79
      send(:"validate_#{param_type}", param, val)
3,412✔
80
    elsif param_type.is_a?(Class) &&
1,934✔
81
          param_type.respond_to?(:descends_from_active_record?)
82
      validate_record_or_id_or_string(param, val, param_type)
1,770✔
83
    elsif param_type.is_a?(Hash)
164✔
84
      validate_enum(param, val, param_type)
164✔
85
    else
NEW
86
      raise("Invalid declaration of :#{param} for #{model} " \
×
87
            "query! (invalid type: #{param_type.class.name})")
88
    end
89
  end
90

91
  def validate_enum(param, val, hash)
1✔
92
    if hash.keys.length != 1
164✔
93
      raise(
×
94
        "Invalid enum declaration for :#{param} for #{model} " \
95
        "query! (wrong number of keys in hash)"
96
      )
97
    end
98

99
    arg_type = hash.keys.first
164✔
100
    set = hash.values.first
164✔
101
    unless set.is_a?(Array)
164✔
102
      raise(
×
103
        "Invalid enum declaration for :#{param} for #{model} " \
104
        "query! (expected value to be an array of allowed values)"
105
      )
106
    end
107

108
    val2 = scalar_validate(param, val, arg_type)
164✔
109
    if (arg_type == :string) && set.include?(val2.to_sym)
163✔
110
      val2 = val2.to_sym
88✔
111
    elsif set.exclude?(val2)
75✔
112
      raise("Value for :#{param} should be one of the following: " \
2✔
113
            "#{set.inspect}.")
114
    end
115
    val2
161✔
116
  end
117

118
  def validate_boolean(param, val)
1✔
119
    case val
1,090✔
120
    # Disable cop because we do mean to symbols with boolean names
121
    # rubocop:disable Lint/BooleanSymbol
122
    when :true, :yes, :on, "true", "yes", "on", "1", 1, true
123
      true
1,086✔
124
    when :false, :no, :off, "false", "no", "off", "0", 0, false, nil
125
      false
4✔
126
    # rubocop:enable Lint/BooleanSymbol
127
    else
NEW
128
      raise("Value for :#{param} should be boolean, got: #{val.inspect}")
×
129
    end
130
  end
131

132
  def validate_integer(param, val)
1✔
133
    if val.is_a?(Integer) || val.is_a?(String) && val.match(/^-?\d+$/)
×
134
      val.to_i
×
135
    elsif val.blank?
×
136
      nil
137
    else
NEW
138
      raise("Value for :#{param} should be an integer, got: #{val.inspect}")
×
139
    end
140
  end
141

142
  def validate_float(param, val)
1✔
143
    if val.is_a?(Integer) || val.is_a?(Float) ||
76✔
144
       (val.is_a?(String) && val.match(/^-?(\d+(\.\d+)?|\.\d+)$/))
5✔
145
      val.to_f
76✔
146
    else
NEW
147
      raise("Value for :#{param} should be a float, got: #{val.inspect}")
×
148
    end
149
  end
150

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

155
      # Cache the instance for later use, in case we both instantiate and
156
      # execute query in the same action.
157
      @params_cache ||= {}
335✔
158
      @params_cache[param] = val
335✔
159
      val.id
335✔
160
    elsif could_be_record_id?(param, val)
1,435✔
161
      val.to_i
1,322✔
162
    elsif val.is_a?(String) && param != :ids
113✔
163
      val
106✔
164
    else
165
      raise("Value for :#{param} should be id, string " \
7✔
166
            "or #{type} instance, got: #{val.inspect}")
167
    end
168
  end
169

170
  def validate_string(param, val)
1✔
171
    if val.is_a?(Integer) || val.is_a?(Float) ||
2,058✔
172
       val.is_a?(String) || val.is_a?(Symbol)
173
      val.to_s
2,050✔
174
    else
175
      raise("Value for :#{param} should be a string or symbol, " \
8✔
176
            "got a #{val.class}: #{val.inspect}")
177
    end
178
  end
179

180
  def validate_id(param, val, type = ActiveRecord::Base)
1✔
181
    if val.is_a?(type)
×
NEW
182
      raise("Value for :#{param} is an unsaved #{type} instance.") unless val.id
×
183

184
      # Cache the instance for later use, in case we both instantiate and
185
      # execute query in the same action.
186
      @params_cache ||= {}
×
NEW
187
      @params_cache[param] = val
×
188
      val.id
×
NEW
189
    elsif could_be_record_id?(param, val)
×
190
      val.to_i
×
191
    else
NEW
192
      raise("Value for :#{param} should be id or #{type} instance, " \
×
193
            "got: #{val.inspect}")
194
    end
195
  end
196

197
  def validate_name(param, val)
1✔
198
    case val
×
199
    when Name
NEW
200
      raise("Value for :#{param} is an unsaved Name instance.") unless val.id
×
201

202
      @params_cache ||= {}
×
NEW
203
      @params_cache[param] = val
×
204
      val.id
×
205
    when String, Integer
206
      val
×
207
    else
NEW
208
      raise("Value for :#{param} should be a Name, String or Integer, " \
×
209
            "got: #{val.class}")
210
    end
211
  end
212

213
  def validate_date(param, val)
1✔
214
    if val.acts_like?(:date)
51✔
215
      format("%04d-%02d-%02d", val.year, val.mon, val.day)
19✔
216
    elsif /^\d\d\d\d(-\d\d?){0,2}$/i.match?(val.to_s) ||
32✔
217
          /^\d\d?(-\d\d?)?$/i.match?(val.to_s)
218
      val
32✔
219
    elsif val.blank? || val.to_s == "0"
×
220
      nil
221
    else
NEW
222
      raise("Value for :#{param} should be a date (YYYY-MM-DD or MM-DD), " \
×
223
            "got: #{val.inspect}")
224
    end
225
  end
226

227
  def validate_time(param, val)
1✔
228
    if val.acts_like?(:time)
92✔
229
      val = val.utc
62✔
230
      format("%04d-%02d-%02d-%02d-%02d-%02d",
62✔
231
             val.year, val.mon, val.day, val.hour, val.min, val.sec)
232
    elsif /^\d\d\d\d(-\d\d?){0,5}$/i.match?(val.to_s)
30✔
233
      val
30✔
234
    elsif val.blank? || val.to_s == "0"
×
235
      nil
236
    else
237
      raise(
×
238
        "Value for :#{param} should be a UTC time (YYYY-MM-DD-HH-MM-SS), " \
239
        "got: #{val.class.name}::#{val.inspect}"
240
      )
241
    end
242
  end
243

244
  def validate_query(param, val)
1✔
245
    case val
45✔
246
    when Query::Base
247
      val.record.id
9✔
248
    when Integer
249
      val
36✔
250
    else
251
      raise(
×
252
        "Value for :#{param} should be a Query class, got: #{val.inspect}"
253
      )
254
    end
255
  end
256

257
  def find_cached_parameter_instance(model, param)
1✔
258
    @params_cache ||= {}
279✔
259
    @params_cache[param] ||= if could_be_record_id?(param, params[param])
279✔
260
                               model.find(params[param])
100✔
261
                             else
262
                               lookup_record_by_name(model, params[param])
1✔
263
                             end
264
  end
265

266
  def get_cached_parameter_instance(param)
1✔
267
    @params_cache ||= {}
×
NEW
268
    @params_cache[param]
×
269
  end
270

271
  def validate_value(param_type, param, val)
1✔
272
    if param_type.is_a?(Array)
4,896✔
273
      array_validate(param, val, param_type.first)
1,539✔
274
    else
275
      scalar_validate(param, val, param_type)
3,357✔
276
    end
277
  end
278

279
  def could_be_record_id?(param, val)
1✔
280
    val.is_a?(Integer) ||
1,536✔
281
      val.is_a?(String) && val.match(/^[1-9]\d*$/) ||
282
      # (blasted admin user has id = 0!)
283
      val.is_a?(String) && (val == "0") && (param == :user)
110✔
284
  end
285

286
  def lookup_record_by_name(type, val)
1✔
287
    lookup = "Lookup::#{type.name.pluralize}".constantize
1✔
288
    result = lookup.new(val).instances&.first
1✔
289
    raise("Couldn't find an id for : #{val.inspect}") unless result
1✔
290

291
    result
1✔
292
  end
293
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc