Class: ApiRegulator::Validator

Inherits:
Object
  • Object
show all
Includes:
ActiveModel::Attributes, ActiveModel::Model, AttributeDefinitionMixin
Defined in:
lib/api_regulator/validator.rb

Class Method Summary collapse

Methods included from AttributeDefinitionMixin

#validate_array_of_objects, #validate_array_of_scalars, #validate_boolean, #validate_integer, #validate_nested_object, #validate_string

Class Method Details

.build_all(api_definitions) ⇒ Object



252
253
254
255
256
257
258
259
260
# File 'lib/api_regulator/validator.rb', line 252

def self.build_all(api_definitions)
  api_definitions.each do |api_definition|
    class_name = build_class_name(api_definition.controller_path, api_definition.action_name)
    validator_class = build_class(api_definition.params, api_definition.action_name)
    silence_warnings do
      Validator.const_set(class_name, validator_class)
    end
  end
end

.build_class(params, action_name) ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/api_regulator/validator.rb', line 286

def self.build_class(params, action_name)
  Class.new do
    include ActiveModel::Model
    include ActiveModel::Attributes
    include AttributeDefinitionMixin

    attr_reader :raw_attributes

    def initialize(attributes = {})
      attributes = {} if attributes.blank?
      @raw_attributes = attributes.deep_symbolize_keys
      allowed_attributes = attributes.slice(*self.class.defined_attributes.map(&:to_sym))
      super(allowed_attributes)
    end

    params.each do |param|
      define_attribute_and_validations(param, action_name:)
    end
  end
end

.build_class_name(controller_path, action, code = nil, prefix: false) ⇒ Object



274
275
276
277
278
279
280
# File 'lib/api_regulator/validator.rb', line 274

def self.build_class_name(controller_path, action, code = nil, prefix: false)
  class_name = "#{controller_path}/#{action}"
  class_name += "/Response#{code}" if code.present?
  class_name = class_name.gsub("/", "_").camelcase
  class_name = "ApiRegulator::Validator::" + class_name if prefix
  class_name
end

.build_response_validators(api_definitions = ApiRegulator.api_definitions) ⇒ Object



262
263
264
265
266
267
268
269
270
271
272
# File 'lib/api_regulator/validator.rb', line 262

def self.build_response_validators(api_definitions = ApiRegulator.api_definitions)
  api_definitions.each do |api_definition|
    api_definition.responses.each do |code, params|
      class_name = build_class_name(api_definition.controller_path, api_definition.action_name, code)
      validator_class = build_class(params.children, api_definition.action_name)
      silence_warnings do
        Validator.const_set(class_name, validator_class)
      end
    end
  end
end

.check_for_extra_response_params!(controller, action, code, body) ⇒ Object



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/api_regulator/validator.rb', line 325

def self.check_for_extra_response_params!(controller, action, code, body)
  actual_keys = flatten_keys(body)

  api_definition ||= ApiRegulator.api_definitions.find do |d|
    d.controller_path == controller && d.action_name == action.to_s
  end
  response_definition = api_definition.responses[code]

  # Identify extra keys
  extra_keys = find_extra_keys(actual_keys, [response_definition])

  # Raise an error if there are unexpected keys
  unless extra_keys.empty?
    raise ApiRegulator::UnexpectedParams.new(extra_keys)
  end
end

.find_extra_keys(actual_keys, params) ⇒ Object



364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/api_regulator/validator.rb', line 364

def self.find_extra_keys(actual_keys, params)
  allowed_keys = params.flat_map(&:allowed_keys)
  allowed_arbitrary_keys = params.flat_map(&:allowed_arbitrary_keys).compact
  extras = []
  actual_keys.each do |key|
    deindexed_key = key.gsub(/\[\d+\]/, "[]")
    next if allowed_keys.include?(deindexed_key)
    next if allowed_arbitrary_keys.any? { |aribitrary_key| deindexed_key.starts_with?(aribitrary_key) }
    extras << key
  end
  extras
end

.flatten_keys(hash, parent_key = nil) ⇒ Object



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/api_regulator/validator.rb', line 342

def self.flatten_keys(hash, parent_key = nil)
  hash.each_with_object([]) do |(key, value), keys|
    next if ["controller", "action"].include?(key)

    full_key = parent_key ? "#{parent_key}.#{key}" : key.to_s

    if value.is_a?(Hash)
      keys.concat(flatten_keys(value, full_key))
    elsif value.is_a?(Array)
      value.each_with_index do |item, index|
        if item.is_a?(Hash)
          keys.concat(flatten_keys(item, "#{full_key}[#{index}]"))
        else
          keys << "#{full_key}[#{index}]"
        end
      end
    else
      keys << full_key
    end
  end
end

.get(controller_path, action, code = nil) ⇒ Object



282
283
284
# File 'lib/api_regulator/validator.rb', line 282

def self.get(controller_path, action, code = nil)
  build_class_name(controller_path, action, code, prefix: true).constantize
end

.validate_response(controller, action, code, body) ⇒ Object



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/api_regulator/validator.rb', line 307

def self.validate_response(controller, action, code, body)
  body = {} if body.empty?

  check_for_extra_response_params!(controller, action, code, body)

  validator_class = get(controller, action, code)

  unless validator_class
    raise "No validator found for controller: #{controller}, action: #{action}, code: #{code}"
  end

  validator = validator_class.new(body)
  unless validator.valid?(:response) # using :response for validation context
    raise ApiRegulator::InvalidParams.new(validator.errors)
  end
end