Class: Roda::RodaPlugins::TypecastParams::Params

Inherits:
Object
  • Object
show all
Defined in:
lib/roda/plugins/typecast_params.rb

Overview

Class handling conversion of submitted parameters to desired types.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(obj) ⇒ Params

Set the object used for converting. Conversion methods will convert members of the passed object.



585
586
587
588
589
590
591
592
593
594
595
596
# File 'lib/roda/plugins/typecast_params.rb', line 585

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

Class Method Details

.handle_type(type, opts = OPTS, &block) ⇒ Object

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

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.



448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/roda/plugins/typecast_params.rb', line 448

def self.handle_type(type, opts=OPTS, &block)
  convert_meth = :"convert_#{type}"
  define_method(convert_meth, &block)

  max_input_bytesize = opts[:max_input_bytesize]
  max_input_bytesize_meth = :"_max_input_bytesize_for_#{type}"
  define_method(max_input_bytesize_meth){max_input_bytesize}

  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, send(max_input_bytesize_meth))
      check_null_byte(val)
      send(convert_meth, val)
    end
  end

  private convert_meth, convert_array_meth, max_input_bytesize_meth
  alias_method max_input_bytesize_meth, max_input_bytesize_meth

  define_method(type) do |key, default=nil|
    process_arg(convert_meth, key, default, send(max_input_bytesize_meth)) if require_hash!
  end

  define_method(:"#{type}!") do |key|
    send(type, key, CHECK_NIL)
  end
end

.max_input_bytesize(type, bytesize) ⇒ Object

Override the maximum input bytesize for the given type. This is mostly useful for overriding the sizes for the default input types.



480
481
482
483
484
485
# File 'lib/roda/plugins/typecast_params.rb', line 480

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

.nest(obj, nesting) ⇒ Object

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.



491
492
493
494
495
496
# File 'lib/roda/plugins/typecast_params.rb', line 491

def self.nest(obj, nesting)
  v = allocate
  v.instance_variable_set(:@nesting, nesting)
  v.send(:initialize, obj)
  v
end

Instance Method Details

#[](key) ⇒ Object

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.



615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
# File 'lib/roda/plugins/typecast_params.rb', line 615

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

#array(type, key, default = nil) ⇒ Object

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.

Raises:



752
753
754
755
756
# File 'lib/roda/plugins/typecast_params.rb', line 752

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, send(:"_max_input_bytesize_for_#{type}")) if require_hash!
end

#array!(type, key, default = nil) ⇒ Object

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.



760
761
762
763
764
765
766
767
768
769
770
771
772
# File 'lib/roda/plugins/typecast_params.rb', line 760

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

#convert!(keys = nil, opts = OPTS) ⇒ Object

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



661
662
663
664
665
666
667
668
669
670
671
672
# File 'lib/roda/plugins/typecast_params.rb', line 661

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

#convert_each!(opts = OPTS, &block) ⇒ Object

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.



685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
# File 'lib/roda/plugins/typecast_params.rb', line 685

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

#dig(type, *nest, key) ⇒ Object

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(:int, 'baz')


739
740
741
# File 'lib/roda/plugins/typecast_params.rb', line 739

def dig(type, *nest, key)
  _dig(false, type, nest, key)
end

#dig!(type, *nest, key) ⇒ Object

Similar to dig, but raises an Error instead of returning nil if no value is found.



744
745
746
# File 'lib/roda/plugins/typecast_params.rb', line 744

def dig!(type, *nest, key)
  _dig(true, type, nest, key)
end

#fetch(key) ⇒ Object

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.



647
648
649
# File 'lib/roda/plugins/typecast_params.rb', line 647

def fetch(key)
  send(:[], key){return(yield if defined?(yield))}
end

#present?(key) ⇒ Boolean

If key is a String Return whether the key is present in the object,

Returns:

  • (Boolean)


599
600
601
602
603
604
605
606
607
608
609
610
611
# File 'lib/roda/plugins/typecast_params.rb', line 599

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