= New Features

* A typecast_params plugin has been added for handling the
  conversion of params to the expected type.  This plugin is
  recommended for all applications that deal with submitted
  parameters.

  Submitted parameters should be considered untrusted input, and in
  standard use with browsers, parameters are submitted as strings
  (or a hash/array containing strings).  In most cases it makes sense
  to explicitly convert the parameter to the desired type.  While this
  can be done via manual conversion:
    
    key = request.params['key'].to_i
    key = nil unless key > 0
    
  the typecast_params plugin adds a friendlier interface:
    
    key = typecast_params.pos_int('key')
    
  As typecast_params is a fairly long method name, you may want to
  consider aliasing it to something more terse in your application,
  such as tp.

  One advantage of using typecast_params is that access or conversion
  errors are raised as a specific exception class
  (Roda::RodaPlugins::TypecastParams::Error).  This allows you to
  handle this specific exception class globally and return an
  appropriate 4xx response to the client.  You can use the
  Error#param_name and Error#reason methods to get more information
  about the error.
    
  typecast_params offers support for default values:
    
    key = typecast_params.pos_int('key', 1)
    
  The default value is only used if no value has been submitted for
  the parameter, or if the conversion of the value results in nil.
  Handling defaults for parameter conversion manually is more
  difficult, since the parameter may not be present at all, or it may
  be present but an empty string because the user did not enter a
  value on the related form.  Use of typecast_params for the
  conversion handles both cases.

  In many cases, parameters should be required, and if they aren't
  submitted, that should be considered an error.  typecast_params
  handles this with ! methods:

    key = typecast_params.pos_int!('key')

  These ! methods raise an error instead of returning nil, and do not
  allow defaults.

  To make it easy to handle cases where many parameters need the same
  conversion done, you can pass an array of keys to a conversion
  method, and it will return an array of converted values:

    key1, key2 = typecast_params.pos_int(['key1', 'key2'])

  This is equivalent to:

    key1 = typecast_params.pos_int('key1')
    key2 = typecast_params.pos_int('key2')

  The ! methods also support arrays of keys, ensuring that all
  parameters have a value:

    key1, key2 = typecast_params.pos_int!(['key1', 'key2'])

  For handling of array parameters, where all entries in the array
  use the same conversion, there is an array method which takes the
  type as the first argument and the keys to convert as the second
  argument:

    keys = typecast_params.array(:pos_int, 'keys')

  If you want to ensure that all entries in the array are converted
  successfully and that there is a value for the array itself, you
  can use array!:

    keys = typecast_params.array!(:pos_int, 'keys')

  This will raise an exception if any of the values in the array for
  parameter keys cannot be converted to a positive integer.

  Both array and array! support default values which are used if no
  value is present for the parameter:

    keys = typecast_params.array(:pos_int, 'keys', [])
    keys = typecast_params.array!(:pos_int, 'keys', [])

  You can also pass an array of keys to array or array!, if you would
  like to perform the same conversion on multiple arrays:

    foo_ids, bar_ids = typecast_params.array!(:pos_int, ['foo_ids', 'bar_ids'])

  The previous examples have shown use of the pos_int method, which
  uses to_i to convert the value to an integer, but returns nil if the
  resulting integer is not positive.  Unless you need to handle
  negative numbers, it is recommended to use pos_int instead of int as
  int will convert invalid values to 0 (since that is how
  <tt>String#to_i</tt> works).

  There are many built in methods for type conversion:

  any :: Returns the value as is without conversion
  str :: Raises if value is not already a string
  nonempty_str :: Raises if value is not already a string, and
                  converts the empty string or string containing only
                  whitespace to nil
  bool :: Converts entry to boolean if in one of the recognized
          formats (case insensitive for strings):
          nil :: nil, ''
          true :: true, 1, '1', 't', 'true', 'yes', 'y', 'on'
          false :: false, 0, '0', 'f', 'false', 'no', 'n', 'off'
          If not in one of those formats, raises an error.
  int :: Converts value to integer using to_i (note that invalid
         input strings will be converted to 0)
  pos_int :: Converts value using to_i, but non-positive values
             are converted to nil
  Integer :: Converts value to integer using
             Kernel::Integer(value, 10)
  float :: Converts value to float using to_f (note that invalid
           input strings will be converted to 0.0)
  Float :: Converts value to float using Kernel::Float(value)
  Hash :: Raises if value is not already a hash
  date :: Converts value to Date using Date.parse(value)
  time :: Converts value to Time using Time.parse(value)
  datetime :: Converts value to DateTime using DateTime.parse(value)
  file :: Raises if value is not already a hash with a :tempfile key
          whose value responds to read (this is the format rack uses
          for uploaded files).

  All of these methods also support ! methods (e.g. pos_int!), and all
  of them can be used in the array and array! methods to support
  arrays of values.

  Since parameter hashes can be nested, the [] method can be used to
  access nested
  hashes:

    # params: {'key'=>{'sub_key'=>'1'}}
    typecast_params['key'].pos_int!('sub_key') # => 1

  This works to an arbitrary depth:

    # params: {'key'=>{'sub_key'=>{'sub_sub_key'=>'1'}}}
    typecast_params['key']['sub_key'].pos_int!('sub_sub_key') # => 1

  And also works with arrays at any depth, if those arrays contain
  hashes:

    # params: {'key'=>[{'sub_key'=>{'sub_sub_key'=>'1'}}]}
    typecast_params['key'][0]['sub_key'].pos_int!('sub_sub_key') # => 1

    # params: {'key'=>[{'sub_key'=>['1']}]}
    typecast_params['key'][0].array!(:pos_int, 'sub_key') # => [1]

  To allow easier access to nested data, there is a dig method:

    typecast_params.dig(:pos_int, 'key', 'sub_key')
    typecast_params.dig(:pos_int, 'key', 0, 'sub_key', 'sub_sub_key')

  dig will return nil if any access while looking up the nested value
  returns nil.  There is also a dig! method, which will raise an Error
  if dig would return nil:

    typecast_params.dig!(:pos_int, 'key', 'sub_key')
    typecast_params.dig!(:pos_int, 'key', 0, 'sub_key', 'sub_sub_key')

  Note that none of these conversion methods modify request.params.
  They purely do the conversion and return the converted value.
  However, in some cases it is useful to do all the conversion up
  front, and then pass a hash of converted parameters to an internal
  method that expects to receive values in specific types.  The
  convert! method does this, and there is also a convert_each! method
  designed for converting multiple values using the same block:

    converted_params = typecast_params.convert! do |tp|
      tp.int('page')
      tp.pos_int!('artist_id')
      tp.array!(:pos_int, 'album_ids')
      tp.convert!('sales') do |stp|
        tp.pos_int!(['num_sold', 'num_shipped'])
      end
      tp.convert!('members') do |mtp|
        mtp.convert_each! do |stp|
          stp.str!(['first_name', 'last_name'])
        end
      end
    end

    # converted_params:
    # {
    #   'page' => 1,
    #   'artist_id' => 2,
    #   'album_ids' => [3, 4],
    #   'sales' => {
    #     'num_sold' => 5,
    #     'num_shipped' => 6
    #   },
    #   'members' => [
    #      {'first_name' => 'Foo', 'last_name' => 'Bar'},
    #      {'first_name' => 'Baz', 'last_name' => 'Quux'}
    #   ]
    # }

  convert! and convert_each! only return values you explicitly specify
  for conversion inside the passed block.
  
  You can specify the :symbolize option to convert! or convert_each!,
  which will symbolize the resulting hash keys:

    converted_params = typecast_params.convert!(symbolize: true) do |tp|
      tp.int('page')
      tp.pos_int!('artist_id')
      tp.array!(:pos_int, 'album_ids')
      tp.convert!('sales') do |stp|
        tp.pos_int!(['num_sold', 'num_shipped'])
      end
      tp.convert!('members') do |mtp|
        mtp.convert_each! do |stp|
          stp.str!(['first_name', 'last_name'])
        end
      end
    end

    # converted_params:
    # {
    #   :page => 1,
    #   :artist_id => 2,
    #   :album_ids => [3, 4],
    #   :sales => {
    #     :num_sold => 5,
    #     :num_shipped => 6
    #   },
    #   :members => [
    #      {:first_name => 'Foo', :last_name => 'Bar'},
    #      {:first_name => 'Baz', :last_name => 'Quux'}
    #   ]
    # }

  Using the :symbolize option makes it simpler to transition from
  untrusted external data (string keys), to trusted data that can be
  used internally (trusted in the sense that the expected types are
  used).

  Note that if there are multiple conversion errors raised inside a
  convert! or convert_each!  block, they are recorded and a single
  Roda::RodaPlugins::TypecastParams::Error instance is raised after
  processing the block.  TypecastParams::Error#param_names can be
  called on the exception to get an array of all parameter names
  with conversion issues, and TypecastParams::Error#all_errors
  can be used to get an array of all Error instances.

  Because of how convert! and convert_each! work, you should avoid
  calling TypecastParams::Params#[] inside the block you pass to
  these methods, because if the #[] call fails, it will skip the
  reminder of the block.

  Be aware that when you use convert! and convert_each!, the
  conversion methods called inside the block may return nil if there
  is a error raised, and nested calls to convert! and convert_each!
  may not return values.

  When loading the typecast_params plugin, a subclass of
  TypecastParams::Params is created specific to the Roda application.
  You can add support for custom types by passing a block when loading
  the typecast_params plugin.  This block is executed in the context
  of the subclass, and calling handle_type in the block can be used to
  add conversion methods.  handle_type accepts a type name and the
  block used to convert the type:

    plugin :typecast_params do
      handle_type(:album) do |value|
        if id = convert_pos_int(val)
          Album[id]
        end
      end
    end

  By default, the typecast_params conversion procs are passed the
  parameter value directly from request.params without modification.
  In some cases, it may be beneficial to strip leading and trailing
  whitespace from parameter string values before processing, which
  you can do by passing the strip: :all> option when loading the
  plugin.

  By design, typecast_params only deals with string keys, it is not
  possible to use symbol keys as arguments to the conversion methods
  and have them converted.
