lib/github_api/api/arguments.rb
# encoding: utf-8
require_relative '../normalizer'
require_relative '../parameter_filter'
require_relative '../params_hash'
require_relative '../validations'
module Github
class API
# A class responsible for handling request arguments
class Arguments
include Normalizer
include ParameterFilter
include Validations
AUTO_PAGINATION = 'auto_pagination'.freeze
# Parameters passed to request
attr_reader :params
# The remaining unparsed arguments
attr_reader :remaining
# The request api
attr_reader :api
# Initialize an Arguments
#
# @param [Hash] options
#
# @option options [Array[String]] :required
# arguments that must be present before request is fired
#
# @option options [Github::API] :api
# the reference to the current api
#
# @api public
def initialize(options = {}, &block)
normalize! options
@api = options.fetch('api')
@required = options.fetch('required', []).map(&:to_s)
@optional = options.fetch('optional', []).map(&:to_s)
@assigns = {}
yield_or_eval(&block)
end
# Specify required attribute(s)
#
# @api public
def require(*attrs, &block)
attrs_clone = attrs.clone
@required = Array(attrs_clone)
self
end
alias :required :require
# Specify optional attribute(s)
#
# @api public
def optional(*attrs, &block)
end
# Hash like access to request arguments
#
# @param [String, Symbol] property
# the property name
#
# @api public
def [](property)
@assigns[property.to_s]
end
def []=(property, value)
@assigns[property.to_s] = value
end
def method_missing(method_name, *args, &block)
if @assigns.key?(method_name.to_s)
self[method_name]
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
@assigns.key?(method_name) || super
end
# Parse arguments to allow for flexible api calls
#
# Arguments can be part of parameters hash or be simple string arguments.
#
# @api public
def parse(*args, &block)
options = ParamsHash.new(args.extract_options!)
normalize! options
if args.size.zero? # Arguments are inside the parameters hash
parse_hash(options)
else
parse_array(*args)
end
@params = options
@remaining = args[@required.size..-1]
extract_pagination(options)
yield_or_eval(&block)
self
end
# Remove unknown keys from parameters hash.
#
# = Parameters
# :recursive - boolean that toggles whether nested filtering should be applied
#
def permit(keys, key=nil, options={})
filter! keys, (key.nil? ? params : params[key]), options if keys.any?
self
end
# Check if required keys are present inside parameters hash.
#
# @api public
def assert_required(*required)
assert_required_keys(required, params)
self
end
# Check if parameters match expected values.
#
# @api public
def assert_values(values, key=nil)
assert_valid_values values, (key.nil? ? params : params[key])
self
end
private
# Parse array arguments and assign in order to required properties
#
# @param [Array[Object]] args
#
# @raise ArgumentError
#
# @return [nil]
#
# @api public
def parse_array(*args)
assert_presence_of(*args)
@required.each_with_index do |req, indx|
@assigns[req] = args[indx]
end
check_requirement!(*args)
end
# Remove required arguments from parameters and
# validate their presence(if not nil or empty string).
#
# @param [Hash[String]] options
#
# @return [nil]
#
# @api private
def parse_hash(options)
options.each { |key, val| remove_required(options, key, val) }
hash = update_required_from_global
check_requirement!(*hash.keys)
end
# Remove required property from hash
#
# @param [Hash[String]] options
# the options to check
#
# @param [String] key
# the key to remove
#
# @param [String] val
# the value to assign
#
# @api private
def remove_required(options, key, val)
if @required.include?(key.to_s)
assert_presence_of(val)
options.delete(key)
@assigns[key.to_s] = val
end
end
# Update required property from globals if not present
#
# @return [Hash[String]]
#
# @api private
def update_required_from_global
@required.reduce({}) do |hash, property|
if @assigns.key?(property)
hash[property] = self[property]
elsif api_property?(property)
hash[property] = api.send(:"#{property}")
self[property] = hash[property]
end
hash
end
end
# Check if api has non-empty property
#
# @param [String] property
# the property to check
#
# @return [Boolean]
#
# @api private
def api_property?(property)
api.respond_to?(:"#{property}") && api.send(:"#{property}")
end
# Check if required arguments are present.
#
#
def check_requirement!(*args)
args_length = args.length
required_length = @required.length
if args_length < required_length
raise ArgumentError, "Wrong number of arguments " \
"(#{args_length} for #{required_length}). " \
"Expected `#{@required.join(', ')}` as required arguments, " \
"but got `#{args.join(", ")}`."
end
end
# Find auto_pagination parameter in options hash
#
def extract_pagination(options)
if (value = options.delete(AUTO_PAGINATION))
api.auto_pagination = value
end
end
# Evaluate a block
#
# @api privte
def yield_or_eval(&block)
return unless block
block.arity > 0 ? yield(self) : instance_eval(&block)
end
end # Arguments
end # Api
end # Github