lib/apidiesel/dsl.rb in apidiesel-0.12 vs lib/apidiesel/dsl.rb in apidiesel-0.13
- old
+ new
@@ -15,10 +15,11 @@
# @yield [Apidiesel::Dsl::ExpectationBuilder]
def expects(&block)
builder = ExpectationBuilder.new
builder.instance_eval(&block)
parameter_validations.concat builder.parameter_validations
+ parameters_to_filter.concat builder.parameters_to_filter
end
# Defines the expected content and format of the response for this API action.
#
# @example
@@ -51,14 +52,17 @@
end
# ExpectationBuilder defines the methods available within an `expects` block
# when defining an API action.
class ExpectationBuilder
- attr_accessor :parameter_validations
+ # @!visibility private
+ attr_accessor :parameter_validations, :parameters_to_filter
+ # @!visibility private
def initialize
@parameter_validations = []
+ @parameters_to_filter = []
end
# Defines a string parameter.
#
# @example
@@ -69,36 +73,40 @@
# end
#
# # This action expects to be given an 'email', which is sent to the API as 'username',
# # and requires either a 'value1', a 'value2' or both to be present.
#
- # @param [Symbol] param_name name of the parameter
- # @param [Hash] *args
- # @option *args [Boolean] :optional (false) defines whether this parameter may be omitted
- # @option *args [Symbol] :optional_if_present param_name is optional, if the parameter given here is present instead
- # @option *args [Symbol] :required_if_present param_name is required if param_name is also present
- # @option *args [Symbol] :submitted_as submit param_name to the API under the name given here
- # @option *args [Object] :default a default parameter to be set when no value is specified
- # @option *args [Enumerable] :allowed_values only accept the values in this Enumerable.
- # If Enumerable is a Hash, use the hash values to define what is actually
- # sent to the server. Example: `:allowed_values => {:foo => "f"}` allows
- # the value ':foo', but sends it as 'f'
- def string(param_name, *args)
- validation_builder(:to_s, param_name, *args)
+ # @!macro [new] expectation_types
+ # @param param_name [Symbol] name of the parameter
+ # @option args [Boolean] :optional (false) defines whether this parameter may be omitted
+ # @option args [Symbol] :optional_if_present param_name is optional, if the parameter given here is present instead
+ # @option args [Symbol] :required_if_present param_name is required if param_name is also present
+ # @option args [Symbol] :submitted_as submit param_name to the API under the name given here
+ # @option args [Object] :default a default parameter to be set when no value is specified
+ # @option args [true, false] :submit (true) set to `false` for arguments that should not be submitted
+ # as API parameters
+ # @option args [Enumerable] :allowed_values only accept the values in this Enumerable.
+ # If Enumerable is a Hash, use the hash values to define what is actually
+ # sent to the server. Example: `:allowed_values => {:foo => "f"}` allows
+ # the value ':foo', but sends it as 'f'
+ # @return [nil]
+ def string(param_name, **args)
+ validation_builder(:to_s, param_name, **args)
+ parameters_to_filter << param_name if args[:submit] == false
end
# Defines an integer parameter.
#
# @example
# expects do
# integer :per_page, :optional => true
# end
#
- # @param (see #string)
- # @option (see #string)
- def integer(param_name, *args)
- validation_builder(:to_i, param_name, *args)
+ # @!macro expectation_types
+ def integer(param_name, **args)
+ validation_builder(:to_i, param_name, **args)
+ parameters_to_filter << param_name if args[:submit] == false
end
# Defines a boolean parameter.
#
# FIXME: sensible duck typing check
@@ -106,33 +114,33 @@
# @example
# expects do
# boolean :per_page, :optional => true
# end
#
- # @param (see #string)
- # @option (see #string)
- def boolean(param_name, *args)
- validation_builder(:to_s, param_name, *args)
+ # @!macro expectation_types
+ def boolean(param_name, **args)
+ validation_builder(:to_s, param_name, **args)
+ parameters_to_filter << param_name if args[:submit] == false
end
# Defines a date, time or datetime parameter.
#
#
# @example
# expects do
# datetime :starts_at, format: '%d-%m-%Y'
# end
#
- # @param (see #string)
- # @option *args [String] :format strftime format string
- # @option (see #string)
+ # @!macro expectation_types
+ # @option args [String] :format a format string as supported by Rubys `#strftime`
def datetime(param_name, **args)
if args[:format]
args[:processor] = ->(value) { value.try(:strftime, args[:format]) }
end
validation_builder(:strftime, param_name, **args)
+ parameters_to_filter << param_name if args[:submit] == false
end
alias_method :time, :datetime
alias_method :date, :datetime
@@ -142,27 +150,27 @@
# @example
# expects do
# object :contract, klass: Contract
# end
#
- # @param (see #string)
- # @option *args [Class] :klass
- # @option (see #string)
+ # @!macro expectation_types
+ # @option args [Class] :klass the expected class of the value
def object(param_name, **args)
type_check = ->(value, param_name) {
unless value.is_a?(args[:klass])
raise Apidiesel::InputError, "arg #{param_name} must be a #{args[:klass].name}"
end
}
validation_builder(type_check, param_name, **args)
+ parameters_to_filter << param_name if args[:submit] == false
end
protected
- def validation_builder(duck_typing_check, param_name, *args)
- options = args.extract_options!
+ def validation_builder(duck_typing_check, param_name, **args)
+ options = args
parameter_validations << lambda do |given_params, processed_params|
if options[:default]
given_params[param_name] ||= options[:default]
end
@@ -209,17 +217,42 @@
end
# FilterBuilder defines the methods available within an `responds_with` block
# when defining an API action.
class FilterBuilder
+ # @!visibility private
attr_accessor :response_filters, :response_formatters
+ # @!visibility private
def initialize
@response_filters = []
@response_formatters = []
end
+ # @!macro [new] filter_types
+ # Returns a $0 from the API response hash
+ #
+ # @overload $0(key, **kargs)
+ # Get the $0 named `key` from the response hash and name it `key` in the result hash
+ #
+ # @param key [String, Symbol]
+ # @option kargs [Proc] :prefilter callback for modifying the value before typecasting
+ # @option kargs [Proc] :postfilter callback for modifying the value after typecasting
+ # @option kargs [Proc] :filter alias for :postfilter
+ # @option kargs [Hash] :map a hash map for replacing the value
+ #
+ # @overload $0(at:, as:, **kargs)
+ # Get the $0 named `at:` from the response hash and name it `as:` in the result hash
+ #
+ # @param at [String, Symbol, Array<Symbol>] response hash key name or key path to lookup
+ # @param as [String, Symbol] result hash key name to return the value as
+ # @option kargs [Proc] :prefilter callback for modifying the value before typecasting
+ # @option kargs [Proc] :postfilter callback for modifying the value after typecasting
+ # @option kargs [Proc] :filter alias for :postfilter
+ # @option kargs [Hash] :map a hash map for replacing the value
+ #
+ # @return [nil]
def value(*args, **kargs)
args = normalize_arguments(args, kargs)
response_formatters << lambda do |data, processed_data|
value = get_value(data, args[:at])
@@ -234,47 +267,69 @@
processed_data
end
end
- # Returns `key` from the API response as a string.
+ # @!macro filter_types
#
- # @param [Symbol] key the key name to be returned as a string
- # @param [Hash] *args
- # @option *args [Symbol] :within look up the key in a namespace (nested hash)
+ # Please note that response value is typecasted to `String` for comparison, so that
+ # for absent values to be considered false, you have to include an empty string.
+ #
+ # @option kargs [Array<#to_s>, #to_s] :truthy ('true') values to be considered true
+ # @option kargs [Array<#to_s>, #to_s] :falsy ('false') values to be considered false
+ def boolean(*args, **kargs)
+ args = normalize_arguments(args, kargs)
+
+ args.reverse_merge!(truthy: 'true', falsy: 'false')
+
+ args[:truthy] = Array(args[:truthy]).map(&:to_s)
+ args[:falsy] = Array(args[:falsy]).map(&:to_s)
+
+ response_formatters << lambda do |data, processed_data|
+ value = get_value(data, args[:at])
+
+ value = apply_filter(args[:prefilter], value)
+
+ value = if args[:truthy].include?(value.to_s)
+ true
+ elsif args[:falsy].include?(value.to_s)
+ false
+ else
+ nil
+ end
+
+ value = apply_filter(args[:postfilter] || args[:filter], value)
+
+ value = args[:map][value] if args[:map]
+
+ processed_data[ args[:as] ] = value
+
+ processed_data
+ end
+ end
+
+ # @!macro filter_types
def string(*args, **kargs)
create_primitive_formatter(:to_s, *args, **kargs)
end
- # Returns `key` from the API response as an integer.
- #
- # @param (see #string)
- # @option (see #string)
+ # @!macro filter_types
def integer(*args, **kargs)
create_primitive_formatter(:to_i, *args, **kargs)
end
- # Returns `key` from the API response as a float.
- #
- # @param (see #string)
- # @option (see #string)
+ # @!macro filter_types
def float(*args, **kargs)
create_primitive_formatter(:to_f, *args, **kargs)
end
- # Returns `key` from the API response as a symbol.
- #
- # @param (see #string)
- # @option (see #string)
+ # @!macro filter_types
def symbol(*args, **kargs)
create_primitive_formatter(:to_sym, *args, **kargs)
end
- # Returns `key` from the API response as DateTime.
- #
- # @param (see #string)
- # @option (see #string)
+ # @!macro filter_types
def datetime(*args, **kargs)
args = normalize_arguments(args, kargs)
args.reverse_merge!(format: '%Y-%m-%d')
response_formatters << lambda do |data, processed_data|
@@ -294,14 +349,11 @@
processed_data
end
end
- # Returns `key` from the API response as Date.
- #
- # @param (see #string)
- # @option (see #string)
+ # @!macro filter_types
def date(*args, **kargs)
args = normalize_arguments(args, kargs)
args.reverse_merge!(format: '%Y-%m-%d')
response_formatters << lambda do |data, processed_data|
@@ -321,14 +373,11 @@
processed_data
end
end
- # Returns `key` from the API response as Time.
- #
- # @param (see #string)
- # @option (see #string)
+ # @!macro filter_types
def time(*args, **kargs)
args = normalize_arguments(args, kargs)
args.reverse_merge!(format: '%Y-%m-%d')
response_formatters << lambda do |data, processed_data|
@@ -348,12 +397,11 @@
processed_data
end
end
- # Returns an array of subhashes
- #
+ # @!macro filter_types
# @example
#
# # Given an API response:
# #
# # {
@@ -373,10 +421,11 @@
# string :name
# integer :product_id
# end
# end
#
+ # @example
# # Given an API response:
# #
# # [
# # {
# # name: 'Catnip 2lbs',
@@ -386,23 +435,16 @@
# # name: 'Catnip 5lbs',
# # order_id: 2004922
# # },
# # ]
#
- # @example
# expects do
# array do
# string :name
# integer :order_id
# end
# end
- #
- # @option *args [Symbol] the key for finding and returning the array
- # (sets both :as and :at)
- # @option **kargs [Symbol] :at which key to find the hash at in the
- # response
- # @option **kargs [Symbol] :as which key to return the result under
def array(*args, **kargs, &block)
unless block.present?
create_primitive_formatter(:to_a, *args, **kargs)
return
end
@@ -445,14 +487,11 @@
array_of_hashes.compact
end
end
end
- # Returns `key` from the API response as a hash.
- #
- # @param (see #string)
- # @option (see #string)
+ # @!macro filter_types
def hash(*args, **kargs, &block)
unless block.present?
create_primitive_formatter(:to_hash, *args, **kargs)
return
end
@@ -481,28 +520,28 @@
processed_data
end
end
- # Returns the API response processed or wrapped in wrapper objects.
- #
# @example
# responds_with do
- # object :issues, :processed_with => lambda { |data| data.delete_if { |k,v| k == 'www_id' } }
+ # object :issues,
+ # processed_with: ->(data) {
+ # data.delete_if { |k,v| k == 'www_id' }
+ # }
# end
#
# @example
#
# responds_with do
- # object :issues, :wrapped_in => Apidiesel::ResponseObjects::Topic
+ # object :issues,
+ # wrapped_in: Apidiesel::ResponseObjects::Topic
# end
#
- # @param [Symbol] key the key name to be wrapped or processed
- # @option *args [Symbol] :within look up the key in a namespace (nested hash)
- # @option *args [Proc] :processed_with yield the data to this Proc for processing
- # @option *args [Class] :wrapped_in wrapper object, will be called as `Object.create(data)`
- # @option *args [Symbol] :as key name to save the result as
+ # @!macro filter_types
+ # @option kargs [Proc] :processed_with yield the data to this Proc for processing
+ # @option kargs [Class] :wrapped_in wrapper object, will be called as `Object.create(data)`
def objects(*args, **kargs)
args = normalize_arguments(args, kargs)
response_formatters << lambda do |data, processed_data|
value = get_value(data, args[:at])
@@ -528,11 +567,11 @@
# Useful for cutting out useless top-level keys
#
# @param [Symbol, Array] key
def set_scope(key)
response_filters << lambda do |data|
- begin; fetch_path(data, *key); rescue => e; binding.pry; end
+ fetch_path(data, *key)
end
end
# Raises an Apidiesel::ResponseError if the callable returns true
#
@@ -541,10 +580,10 @@
# response_error_if ->(data) { data[:code] != 0 },
# message: ->(data) { data[:message] }
#
# @param [Lambda, Proc] callable
# @param [String, Lambda, Proc] message
- # @raises [Apidiesel::ResponseError]
+ # @raise [Apidiesel::ResponseError]
def response_error_if(callable, message:)
response_formatters << lambda do |data, processed_data|
return processed_data unless callable.call(data)
message = message.is_a?(String) ? message : message.call(data)
\ No newline at end of file