=head2 validate

  # input from hash
  $self->validate(
    { foo => 123, name => 'batman' },
    foo => sub {
      die "Not a number" unless =~ /^\d+$/;
      die "The number is too high" if $_ > 1000;
    },
    name => sub {
      die "Required" unless length $_;
    },
    sub {
      my($self, $data) = @_;
      # $@ contains a hash ref with key/value of invalid data
      # $data = hash ref on success
    },
  );

  # require $self->param() method
  $self->validate(
    @rules,
    sub {
      my($self, $data) = @_;
      # $@ contains a hash ref with key/value of invalid data
      # $data = hash ref on success
    },
  );

=cut

sub validate {
  my $cb = pop;
  my $self = shift;
  my $input = ref $_[0] eq 'HASH' ? shift : undef;
  my $retriever = $input ? sub { $input->{$_[1]} } : 'param';
  my $data = {};
  my $errors;

  while(@_) {
    my($name, $validator) = (shift, shift);
    eval {
      local $_ = Mojo::Util::trim($self->$retriever($name));
      $self->$validator;
      $data->{$name} = $_;
      1;
    } or do {
      $errors->{$name} = $@;
      $errors->{$name} =~ s/ at \S+.*//s;
    };
  }

  $@ = $errors;
  $self->stash(errors => $errors) if $self->can('stash');
  $self->$cb($errors ? undef : $data);
  $self;
}