class Rester::Service::Resource::Params

Constants

BASIC_TYPES
DEFAULT_OPTS
DEFAULT_TYPE_MATCHERS

Attributes

options[R]

Public Class Methods

new(opts={}, &block) click to toggle source
# File lib/rester/service/resource/params.rb, line 15
def initialize(opts={}, &block)
  @options = DEFAULT_OPTS.merge(opts).freeze
  @_dynamic_fields = []
  @_required_fields = []
  @_defaults = {}
  @_all_fields = []

  # Default "validator" is to just treat the param as a string.
  @_validators = Hash.new([String, {}])

  instance_eval(&block) if block_given?
end

Public Instance Methods

Boolean(name, opts={}) click to toggle source

Need to have special handling for Boolean since Ruby doesn't have a Boolean type, instead it has TrueClass and FalseClass…

# File lib/rester/service/resource/params.rb, line 104
def Boolean(name, opts={})
  _add_validator(name, :boolean, opts)
end
freeze() click to toggle source
Calls superclass method
# File lib/rester/service/resource/params.rb, line 35
def freeze
  @_validators.freeze
  @_required_fields.freeze
  @_dynamic_fields.freeze
  @_defaults.freeze
  @_all_fields.freeze
  super
end
required?(key) click to toggle source
# File lib/rester/service/resource/params.rb, line 77
def required?(key)
  @_required_fields.include?(key.to_sym)
end
strict?() click to toggle source

Whether or not validation will be done strictly (i.e., only specified params will be allowed).

# File lib/rester/service/resource/params.rb, line 31
def strict?
  !!options[:strict]
end
use(params) click to toggle source
# File lib/rester/service/resource/params.rb, line 72
def use(params)
  _merge_params(params)
  nil
end
validate(params) click to toggle source
# File lib/rester/service/resource/params.rb, line 44
def validate(params)
  param_keys = params.keys.map(&:to_sym)
  default_keys = @_defaults.keys

  unless (missing = @_required_fields - param_keys - default_keys).empty?
    _error!("missing params: #{missing.join(', ')}")
  end

  _validate_strict(param_keys)

  validated_params = Hash[
    params.map { |key, value| [key.to_sym, validate!(key.to_sym, value)] }
  ]

  @_defaults.merge(validated_params)
end
validate!(key, value) click to toggle source
# File lib/rester/service/resource/params.rb, line 61
def validate!(key, value)
  if @_validators.key?(key)
    klass, opts = @_validators[key]
  else
    dynamic_key = @_dynamic_fields.find { |r| r.match(key) }
    klass, opts = @_validators[dynamic_key]
  end

  _validate(key, value, klass, opts)
end

Protected Instance Methods

all_fields() click to toggle source
# File lib/rester/service/resource/params.rb, line 122
def all_fields
  @_all_fields.dup
end
defaults() click to toggle source
# File lib/rester/service/resource/params.rb, line 118
def defaults
  @_defaults.dup
end
dynamic_fields() click to toggle source
# File lib/rester/service/resource/params.rb, line 114
def dynamic_fields
  @_dynamic_fields.dup
end
required_params() click to toggle source
# File lib/rester/service/resource/params.rb, line 110
def required_params
  @_required_fields.dup
end
validators() click to toggle source
# File lib/rester/service/resource/params.rb, line 126
def validators
  @_validators.dup
end

Private Instance Methods

_add_validator(name, klass, opts) click to toggle source
# File lib/rester/service/resource/params.rb, line 142
def _add_validator(name, klass, opts)
  fail 'must specify param name' unless name
  fail 'validation options must be a Hash' unless opts.is_a?(Hash)
  default_opts = { match: DEFAULT_TYPE_MATCHERS[klass] }
  opts = default_opts.merge(opts)

  if name.is_a?(Regexp)
    @_dynamic_fields << name
  else
    name = name.to_sym
    @_required_fields << name if opts.delete(:required)
    default = opts.delete(:default)
  end

  @_all_fields << name
  @_validators[name] = [klass, opts]

  if name.is_a?(Symbol) && default
    _validate_default(name, default)
    @_defaults[name] = default
  end

  nil
end
_error!(message) click to toggle source
# File lib/rester/service/resource/params.rb, line 330
def _error!(message)
  Errors.throw_error!(Errors::ValidationError, message)
end
_merge_params(params) click to toggle source
# File lib/rester/service/resource/params.rb, line 334
def _merge_params(params)
  @_validators       = @_validators.merge!(params.validators)
  @_defaults         = @_defaults.merge!(params.defaults)
  @_required_fields |= params.required_params
  @_dynamic_fields  |= params.dynamic_fields
  @_all_fields      |= params.all_fields
end
_parse_with_class(klass, value) click to toggle source
# File lib/rester/service/resource/params.rb, line 244
def _parse_with_class(klass, value)
  if klass == String
    value
  elsif klass == Integer
    value.to_i
  elsif klass == Float
    value.to_f
  elsif klass == Symbol
    value.to_sym
  elsif klass == :boolean
    value.downcase == 'true' ? true : false
  else
    klass.parse(value)
  end
end
_valid_type?(obj, type) click to toggle source
# File lib/rester/service/resource/params.rb, line 297
def _valid_type?(obj, type)
  case type
  when :boolean
    obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
  else
    obj.is_a?(type)
  end
end
_validate(key, value, klass, opts) click to toggle source

Validates and parses a given value. `klass` is the intended type for the value (e.g., String, Integer, Array, etc.). `opts` contains the validation options.

# File lib/rester/service/resource/params.rb, line 203
def _validate(key, value, klass, opts)
  case value
  when String
    _validate_str(key, value, klass, opts)
  when Array
    _validate_array(key, value, klass, opts)
  when Hash
    _validate_hash(key, value, klass, opts)
  when NilClass
    _validate_required(key, false)
  else
    _error!("unexpected value type for #{key}: #{value.class}")
  end
end
_validate_array(key, value, klass, opts) click to toggle source
# File lib/rester/service/resource/params.rb, line 231
def _validate_array(key, value, klass, opts)
  _error!("unexpected Array for #{key}") unless klass == Array
  type = (opts = opts.dup).delete(:type) || String

  value.each_with_index
    .map { |e, i| _validate("#{key}[#{i}]", e, type, opts) }
end
_validate_default(key, default) click to toggle source

Validates a default value specified in the params block. Raises validation error if necessary.

# File lib/rester/service/resource/params.rb, line 182
def _validate_default(key, default)
  error = catch(:error) do
    type = @_validators[key].first

    unless _valid_type?(default, type)
      # The .camelcase here is for when type = 'boolean'
      _error!("default for #{key} should be of "\
        "type #{type.to_s.camelcase}")
    end

    validate!(key.to_sym, default.to_s)
    nil
  end

  raise error if error
end
_validate_hash(key, value, klass, opts) click to toggle source
# File lib/rester/service/resource/params.rb, line 239
def _validate_hash(key, value, klass, opts)
  _error!("unexpected Hash for #{key}") unless klass == Hash
  (validator = opts[:use]) && validator.validate(value)
end
_validate_match(key, str, matcher) click to toggle source

To be called before the incoming string is parsed into the object.

# File lib/rester/service/resource/params.rb, line 308
def _validate_match(key, str, matcher)
  unless matcher.match(str)
    _error!("#{key} does not match #{matcher.inspect}")
  end
end
_validate_method(key, obj, opt, value) click to toggle source
# File lib/rester/service/resource/params.rb, line 320
def _validate_method(key, obj, opt, value)
  unless (meth = obj.respond_to?(opt) && obj.method(opt))
    _error!("#{key} does not respond to #{opt.inspect}")
  end

  unless meth.call(*value)
    _error!("#{key} failed #{opt}(#{[value].flatten.join(',')}) validation")
  end
end
_validate_obj(key, obj, opts) click to toggle source
# File lib/rester/service/resource/params.rb, line 260
def _validate_obj(key, obj, opts)
  fail if obj.nil? # Assert, at this point should be guaranteed

  opts.each do |opt, value|
    case opt
    when :within
      _validate_within(key, obj, value)
    when :match
      # Nop - This is evaluated before the incoming string is parsed.
    else
      _validate_method(key, obj, opt, value) unless obj.nil?
    end
  end
end
_validate_required(key, is_defined) click to toggle source
# File lib/rester/service/resource/params.rb, line 283
def _validate_required(key, is_defined)
  unless is_defined
    _, key, index = /(\w+)(\[\d+\])?/.match(key).to_a

    if required?(key)
      if index
        _error!("#{key} cannot contain null elements")
      else
        _error!("#{key} cannot be null")
      end
    end
  end
end
_validate_str(key, value, klass, opts) click to toggle source
# File lib/rester/service/resource/params.rb, line 218
def _validate_str(key, value, klass, opts)
  fail unless value.is_a?(String) # assert
  if [Array, Hash].include?(klass)
    _error!("expected #{key} to be of type #{klass}")
  end

  _validate_match(key, value, opts[:match]) if opts[:match]
  _parse_with_class(klass, value).tap do |obj|
    _validate_type(key, obj, klass) if obj
    _validate_obj(key, obj, opts)
  end
end
_validate_strict(keys) click to toggle source
# File lib/rester/service/resource/params.rb, line 167
def _validate_strict(keys)
  if strict?
    unexpected = (keys - @_all_fields).reject do |k|
      @_dynamic_fields.find { |f| f.match(k) }
    end

    unless unexpected.empty?
      _error!("unexpected params: #{unexpected.join(', ')}")
    end
  end
end
_validate_type(key, obj, type) click to toggle source
# File lib/rester/service/resource/params.rb, line 275
def _validate_type(key, obj, type)
  unless _valid_type?(obj, type)
    # The .camelcase here is for when type = 'boolean'
    _error!("#{key} should be #{type.to_s.camelcase} but "\
      "got #{obj.class}")
  end
end
_validate_within(key, obj, value) click to toggle source
# File lib/rester/service/resource/params.rb, line 314
def _validate_within(key, obj, value)
  unless value.include?(obj)
    _error!("#{key} not within #{value.inspect}")
  end
end
method_missing(meth, *args) click to toggle source
Calls superclass method
# File lib/rester/service/resource/params.rb, line 132
def method_missing(meth, *args)
  if meth.to_s.match(/\A[A-Z][A-Za-z]+\z/)
    name = args.shift
    opts = args.shift || {}
    _add_validator(name, self.class.const_get(meth), opts)
  else
    super
  end
end