class Roda::RodaPlugins::TypecastParams::Params
Class handling conversion of submitted parameters to desired types.
Attributes
Whether to symbolize keys when capturing. Note that the method is renamed to symbolize?
.
Whether to symbolize keys when capturing. Note that the method is renamed to symbolize?
.
Public Class Methods
Source
# File lib/roda/plugins/typecast_params.rb, line 457 def self.handle_type(type, opts=OPTS, &block) convert_meth = :"convert_#{type}" define_method(convert_meth, &block) convert_array_meth = :"_convert_array_#{type}" define_method(convert_array_meth) do |v| raise Error, "expected array but received #{v.inspect}" unless v.is_a?(Array) v.map! do |val| check_allowed_bytesize(val, _max_input_bytesize_for(type)) check_null_byte(val) send(convert_meth, val) end end private convert_meth, convert_array_meth invalid_value_message(type, opts[:invalid_value_message]) max_input_bytesize(type, opts[:max_input_bytesize]) define_method(type) do |key, default=nil| process_arg(convert_meth, key, default, type) if require_hash! end define_method(:"#{type}!") do |key| send(type, key, CHECK_NIL) end end
Handle conversions for the given type using the given block. For a type named foo
, this will create the following methods:
-
foo(key, default=nil)
-
foo!(key)
-
convert_foo(value) # private
-
_convert_array_foo(value) # private
-
_invalid_value_message_for_foo # private
-
_max_input_bytesize_for_foo # private
This method is used to define all type conversions, even the built in ones. It can be called in subclasses to setup subclass-specific types.
Source
# File lib/roda/plugins/typecast_params.rb, line 486 def self.invalid_value_message(type, message) invalid_value_message_meth = :"_invalid_value_message_for_#{type}" define_method(invalid_value_message_meth){message} private invalid_value_message_meth alias_method invalid_value_message_meth, invalid_value_message_meth end
Set the invalid message for the given type.
Source
# File lib/roda/plugins/typecast_params.rb, line 494 def self.max_input_bytesize(type, bytesize) max_input_bytesize_meth = :"_max_input_bytesize_for_#{type}" define_method(max_input_bytesize_meth){bytesize} private max_input_bytesize_meth alias_method max_input_bytesize_meth, max_input_bytesize_meth end
Set the maximum input bytesize for the given type.
Source
# File lib/roda/plugins/typecast_params.rb, line 505 def self.nest(obj, nesting) v = allocate v.instance_variable_set(:@nesting, nesting) v.send(:initialize, obj) v end
Create a new instance with the given object and nesting level. obj
should be an array or hash, and nesting
should be an array. Designed for internal use, should not be called by external code.
Source
# File lib/roda/plugins/typecast_params.rb, line 599 def initialize(obj) case @obj = obj when Hash, Array # nothing else if @nesting handle_error(nil, (@obj.nil? ? :missing : :invalid_type), "value of #{param_name(nil)} parameter not an array or hash: #{obj.inspect}", true) else handle_error(nil, :invalid_type, "parameters given not an array or hash: #{obj.inspect}", true) end end end
Set the object used for converting. Conversion methods will convert members of the passed object.
Public Instance Methods
Source
# File lib/roda/plugins/typecast_params.rb, line 629 def [](key) @subs ||= {} if sub = @subs[key] return sub end if @obj.is_a?(Array) unless key.is_a?(Integer) handle_error(key, :invalid_type, "invalid use of non-integer key for accessing array: #{key.inspect}", true) end else if key.is_a?(Integer) handle_error(key, :invalid_type, "invalid use of integer key for accessing hash: #{key}", true) end end v = @obj[key] v = yield if v.nil? && defined?(yield) begin sub = self.class.nest(v, Array(@nesting) + [key]) rescue => e handle_error(key, :invalid_type, e, true) end @subs[key] = sub sub.sub_capture(@capture, @symbolize, @skip_missing) sub end
Return a new Params
instance for the given key
. The value of key
should be an array if key
is an integer, or hash otherwise.
Source
# File lib/roda/plugins/typecast_params.rb, line 766 def array(type, key, default=nil) meth = :"_convert_array_#{type}" raise ProgrammerError, "no typecast_params type registered for #{type.inspect}" unless respond_to?(meth, true) process_arg(meth, key, default, type) if require_hash! end
Convert the value of key
to an array of values of the given type
. If default
is given, any nil
values in the array are replaced with default
. If key
is an array then this returns an array of arrays, one for each respective value of key
. If there is no value for key
, nil is returned instead of an array.
Source
# File lib/roda/plugins/typecast_params.rb, line 774 def array!(type, key, default=nil) v = array(type, key, default) if key.is_a?(Array) key.zip(v).each do |k, arr| check_array!(k, arr) end else check_array!(key, v) end v end
Call array
with the type
, key
, and default
, but if the return value is nil or any value in the returned array is nil
, raise an Error
.
Source
# File lib/roda/plugins/typecast_params.rb, line 675 def convert!(keys=nil, opts=OPTS) if keys.is_a?(Hash) opts = keys keys = nil end _capture!(:nested_params, opts) do if sub = subkey(Array(keys).dup, opts.fetch(:raise, true)) yield sub end end end
Captures conversions inside the given block, and returns a hash of all conversions, including conversions of subkeys. keys
should be an array of subkeys to access, or nil to convert the current object. If keys
is given as a hash, it is used as the options hash. Options:
- :raise
-
If set to false, do not raise errors for missing keys
- :skip_missing
-
If set to true, does not store values if the key is not present in the params.
- :symbolize
-
Convert any string keys in the resulting hash and for any conversions below
Source
# File lib/roda/plugins/typecast_params.rb, line 699 def convert_each!(opts=OPTS, &block) np = !@capture _capture!(nil, opts) do case keys = opts[:keys] when nil keys = (0...@obj.length) valid = if @obj.is_a?(Array) true else keys = keys.map(&:to_s) keys.all?{|k| @obj.has_key?(k)} end unless valid handle_error(nil, :invalid_type, "convert_each! called on object not an array or hash with keys '0'..'N'") next end when Array # nothing to do when Proc, Method keys = keys.call(@obj) else raise ProgrammerError, "unsupported convert_each! :keys option: #{keys.inspect}" end keys.map do |i| begin if v = subkey([i], opts.fetch(:raise, true)) yield v v.nested_params if np end rescue => e handle_error(i, :invalid_type, e) end end end end
Runs conversions similar to convert! for each key specified by the :keys option. If :keys option is not given and the object is an array, runs conversions for all entries in the array. If the :keys option is not given and the object is a Hash with string keys ‘0’, ‘1’, …, ‘N’ (with no skipped keys), runs conversions for all entries in the hash. If :keys option is a Proc or a Method, calls the proc/method with the current object, which should return an array of keys to use. Supports options given to convert!
, and this additional option:
- :keys
-
The keys to extract from the object. If a proc or method, calls the value with the current object, which should return the array of keys to use.
Source
# File lib/roda/plugins/typecast_params.rb, line 753 def dig(type, *nest, key) _dig(false, type, nest, key) end
Convert values nested under the current obj. Traverses the current object using nest
, then converts key
on that object using type
:
tp.dig(:pos_int, 'foo') # tp.pos_int('foo') tp.dig(:pos_int, 'foo', 'bar') # tp['foo'].pos_int('bar') tp.dig(:pos_int, 'foo', 'bar', 'baz') # tp['foo']['bar'].pos_int('baz')
Returns nil if any of the values are not present or not the expected type. If the nest path results in an object that is not an array or hash, then raises an Error
.
You can use dig
to get access to nested arrays by using :array
or :array!
as the first argument and providing the type in the second argument:
tp.dig(:array, :pos_int, 'foo', 'bar', 'baz') # tp['foo']['bar'].array(:pos_int, 'baz')
Source
# File lib/roda/plugins/typecast_params.rb, line 758 def dig!(type, *nest, key) _dig(true, type, nest, key) end
Similar to dig
, but raises an Error
instead of returning nil
if no value is found.
Source
# File lib/roda/plugins/typecast_params.rb, line 661 def fetch(key) send(:[], key){return(yield if defined?(yield))} end
Return the nested value for key. If there is no nested_value for key
, calls the block to return the value, or returns nil if there is no block given.
Source
# File lib/roda/plugins/typecast_params.rb, line 613 def present?(key) case key when String !any(key).nil? when Array key.all? do |k| raise ProgrammerError, "non-String element in array argument passed to present?: #{k.inspect}" unless k.is_a?(String) !any(k).nil? end else raise ProgrammerError, "unexpected argument passed to present?: #{key.inspect}" end end
If key is a String Return whether the key is present in the object,
Protected Instance Methods
Source
# File lib/roda/plugins/typecast_params.rb, line 791 def nested_params return @nested_params if @nested_params params = @params if @subs @subs.each do |key, v| if key.is_a?(String) && symbolize? key = key.to_sym end params[key] = v.nested_params end end params end
Recursively descendent into all known subkeys and get the converted params from each.
Source
# File lib/roda/plugins/typecast_params.rb, line 845 def sub_capture(capture, symbolize, skip_missing) if @capture = capture @symbolize = symbolize @skip_missing = skip_missing @params = @obj.class.new end end
Inherit given capturing and symbolize setting from parent object.
Source
# File lib/roda/plugins/typecast_params.rb, line 809 def subkey(keys, do_raise) unless key = keys.shift return self end reason = :invalid_type case key when String unless @obj.is_a?(Hash) raise Error, "parameter #{param_name(nil)} is not a hash" if do_raise return end present = !@obj[key].nil? when Integer unless @obj.is_a?(Array) raise Error, "parameter #{param_name(nil)} is not an array" if do_raise return end present = key < @obj.length else raise ProgrammerError, "invalid argument used to traverse parameters: #{key.inspect}" end unless present reason = :missing raise Error, "parameter #{param_name(key)} is not present" if do_raise return end self[key].subkey(keys, do_raise) rescue => e handle_error(key, reason, e) end
Recursive method to get subkeys.
Private Instance Methods
Source
# File lib/roda/plugins/typecast_params.rb, line 862 def _capture!(ret, opts) previous_symbolize = @symbolize previous_skip_missing = @skip_missing unless cap = @capture @params = @obj.class.new @subs.clear if @subs capturing_started = true cap = @capture = [] end if opts.has_key?(:symbolize) @symbolize = !!opts[:symbolize] end if opts.has_key?(:skip_missing) @skip_missing = !!opts[:skip_missing] end begin v = yield rescue Error => e cap << e unless cap.last == e end if capturing_started unless cap.empty? e = cap[0] e.all_errors = cap raise e end if ret == :nested_params nested_params else v end end ensure @nested_params = nil if capturing_started # Unset capturing if capturing was already started. @capture = nil else # If capturing was not already started, update cached nested params # before resetting symbolize setting. @nested_params = nested_params end @symbolize = previous_symbolize @skip_missing = previous_skip_missing end
Internals of convert! and convert_each!.
Source
# File lib/roda/plugins/typecast_params.rb, line 927 def _dig(force, type, nest, key) if type == :array || type == :array! conv_type = nest.shift unless conv_type.is_a?(Symbol) raise ProgrammerError, "incorrect subtype given when using #{type} as argument for dig/dig!: #{conv_type.inspect}" end meth = type type = conv_type args = [meth, type] else meth = type args = [type] end unless respond_to?("_convert_array_#{type}", true) raise ProgrammerError, "no typecast_params type registered for #{meth.inspect}" end if v = subkey(nest, force) v.send(*args, key, (CHECK_NIL if force)) end end
Internals of dig/dig!
Source
# File lib/roda/plugins/typecast_params.rb, line 1026 def _invalid_value_message_for(type) send(:"_invalid_value_message_for_#{type}") end
The invalid message to use if the given type conversion fails, which may be nil to use the default.
Source
# File lib/roda/plugins/typecast_params.rb, line 1031 def _max_input_bytesize_for(type) send(:"_max_input_bytesize_for_#{type}") end
The maximum input bytesize for the given type, which may be nil.
Source
# File lib/roda/plugins/typecast_params.rb, line 1112 def _string_parse!(klass, v) klass.parse(v) end
Handle parsing for string values passed to parse!.
Source
# File lib/roda/plugins/typecast_params.rb, line 1036 def check_allowed_bytesize(v, max) if max && v.is_a?(String) && v.bytesize > max handle_error(nil, :too_long, "string parameter is too long for type", true) end end
Raise an Error
if the value is a string with bytesize over max (if max is given)
Source
# File lib/roda/plugins/typecast_params.rb, line 916 def check_array!(key, arr) if arr if arr.any?{|val| val.nil?} handle_error(key, :invalid_type, "invalid value in array parameter #{param_name(key)}") end else handle_error(key, :missing, "missing parameter for #{param_name(key)}") end end
Raise an error if the array given does contains nil values.
Source
# File lib/roda/plugins/typecast_params.rb, line 1043 def check_null_byte(v) if v.is_a?(String) && v.index("\0") handle_error(nil, :null_byte, "string parameter contains null byte", true) end end
Raise an Error
if the value is a string containing a null byte.
Source
# File lib/roda/plugins/typecast_params.rb, line 970 def handle_error(key, reason, e, do_raise=false) case e when String handle_error(key, reason, Error.new(e), do_raise) when Error, ArgumentError if @capture && (le = @capture.last) && le == e raise e if do_raise return end e = Error.create(keys(key), reason, e) if @capture @capture << e raise e if do_raise nil else raise e end else raise e end end
Handle any conversion errors. By default, reraises Error
instances with the keys set, converts ::ArgumentError instances to Error
instances, and reraises other exceptions.
Source
# File lib/roda/plugins/typecast_params.rb, line 964 def keys(key) Array(@nesting) + Array(key) end
If key
is not nil
, add it to the given nesting. Otherwise, just return the given nesting. Designed for use in setting the keys
values in raised exceptions.
Source
# File lib/roda/plugins/typecast_params.rb, line 951 def param_name(key) first, *rest = keys(key) if first v = first.dup rest.each do |param| v << "[#{param}]" end v end end
Format a reasonable parameter name value, for use in exception messages.
Source
# File lib/roda/plugins/typecast_params.rb, line 1080 def param_value(key) @obj[key] end
Get the value for the given key in the object.
Source
# File lib/roda/plugins/typecast_params.rb, line 1100 def parse!(klass, v) case v when '' nil when String _string_parse!(klass, v) else raise Error, "unexpected value received: #{v.inspect}" end end
Helper for conversion methods where ” should be considered nil, and only String values should be converted by calling parse
on the given klass
.
Source
# File lib/roda/plugins/typecast_params.rb, line 1051 def process(meth, key, default, type) orig_v = v = param_value(key) if v.nil? if default == CHECK_NIL handle_error(key, :missing, "missing parameter for #{param_name(key)}") end else check_allowed_bytesize(v, _max_input_bytesize_for(type)) check_null_byte(v) v = send(meth, v) end if v.nil? if !orig_v.nil? && default == CHECK_NIL invalid_value_message = _invalid_value_message_for(type) invalid_value_message ||= "invalid parameter value for" handle_error(key, :invalid_value, "#{invalid_value_message} #{param_name(key)}") end default else v end rescue => e handle_error(key, meth.to_s.sub(/\A_?convert_/, '').to_sym, e) end
Get the value of key
for the object, and convert it to the expected type using meth
. If the value either before or after conversion is nil, return the default
value.
Source
# File lib/roda/plugins/typecast_params.rb, line 1002 def process_arg(meth, key, default, type) case key when String v = process(meth, key, default, type) if @capture key = key.to_sym if symbolize? if !@skip_missing || @obj.has_key?(key) @params[key] = v end end v when Array key.map do |k| raise ProgrammerError, "non-String element in array argument passed to typecast_params: #{k.inspect}" unless k.is_a?(String) process_arg(meth, k, default, type) end else raise ProgrammerError, "Unsupported argument for typecast_params conversion method: #{key.inspect}" end end
If key
is not an array, convert the value at the given key
using the meth
method and default
value. If key
is an array, return an array with the conversion done for each respective member of key
.
Source
# File lib/roda/plugins/typecast_params.rb, line 996 def require_hash! @obj.is_a?(Hash) || handle_error(nil, :invalid_type, "expected hash object in #{param_name(nil)} but received array object") end
Issue an error unless the current object is a hash. Used to ensure we don’t try to access entries if the current object is an array.
Source
# File lib/roda/plugins/typecast_params.rb, line 1086 def string_or_numeric!(v) case v when '' nil when String, Numeric true else raise Error, "unexpected value received: #{v.inspect}" end end
Helper for conversion methods where ” should be considered nil, and only String or Numeric values should be converted.