bradcathey has asked for the wisdom of the Perl Monks concerning the following question:

Fellow Monks,
I know, sounds elementary, but this one has a slight twist I'm not getting my head around. I'm untainting user input and generating error messages. I have the following working fine, but would like to change things to include my error messages in the Validate module and not my script (just for cleanliness). So, the original (stripped down for clarity):
#!/usr/bin/perl -T print "Content-type: text/html\n\n"; use lib "/home/wbc/www/perllib"; use warnings; use Validate; use strict; use CGI; my $query = new CGI; my $age = Validate->numbers ($query->param('age')); push @errors, "Please, use only numbers.\n" unless $age; __END__ package Validate; sub numbers { my ($class, $value) = @_; return unless $value =~ /^([0-9 \.-]*)$/; return "$1"; } 1;
And what I would like to do...
Something like this, but I know this isn't right, and nothing I tried is working.
#!/usr/bin/perl -T print "Content-type: text/html\n\n"; use lib "/home/wbc/www/perllib"; use warnings; use Validate; use strict; use CGI; my $query = new CGI; my $age = Validate->numbers ($query->param('age')); push @errors, $msg unless $age; #something like this but know + it's not correct __END__ package Validate; sub numbers { my ($class, $value) = @_; my $msg = "Please, use only numbers."; return unless $value =~ /^([0-9 \.-]*)$/; return "$1"; } 1;
So, how do I define the error message in the module and then pass it back to the calling script?

Thanks in advance.

—Brad
"A little yeast leavens the whole dough."

Replies are listed 'Best First'.
Re: Passing a value from a module
by saintmike (Vicar) on Apr 14, 2004 at 20:43 UTC
    If I understand you correctly, you want to access a variable in a module from somewhere else? If it's a global variable declared like
    package Validate; our $msg;
    in the module, you can access it from the main program by fully qualifying the name like in $Validate::msg.

    If you don't like globals, try a OO-aproach: an object of type Validate offers the numbers() method and carries a $self->{msg} instance variable, which you can offer via the msg() method by the Validate class.

Re: Passing a value from a module
by revdiablo (Prior) on Apr 14, 2004 at 21:25 UTC

    One approach is to use die to send the exception (with a message) and blockwise eval to catch it. Example:

    { package foo; sub add { my ($x, $y) = @_; die "not a number\n" unless $x =~ /\d/ and $y =~ /\d/; $x + $y } } # catch any exceptions eval { print "1+2==", foo::add(1, 2); print "a+2==", foo::add("a", 2); }; # handle any exceptions caught by eval if ($@) { push @errors, $@; } # program continues...

    Note that the subroutine being in a module doesn't really change things much. Your question is about returning out-of-band data. Using a global variable is one approach, and here I've shown another. These techiques work the same whether the subroutine is in the same package or not.

Re: Passing a value from a module
by sgifford (Prior) on Apr 15, 2004 at 02:33 UTC
    A common way to do this is to provide a method in your module that will return the last error that occured. Something like:
    ... my $age = Validate->numbers ($query->param('age')); push @errors, Validate->lasterr unless $age; ... package Validate; our $lasterr; sub numbers { my ($class, $value) = @_; if ($value $lasterr = "Please, use only numbers."; return undef unless $value =~ /^([0-9 \.-]*)$/; return "$1"; } sub lasterr { return $lasterr; } 1;
Re: Passing a value from a module
by Stevie-O (Friar) on Apr 14, 2004 at 21:51 UTC
    Or you could just fill $@, a conveniently global variable that will hang around until the next eval...
    --Stevie-O
    $"=$,,$_=q>|\p4<6 8p<M/_|<('=> .q>.<4-KI<l|2$<6%s!<qn#F<>;$, .=pack'N*',"@{[unpack'C*',$_] }"for split/</;$_=$,,y[A-Z a-z] {}cd;print lc
Re: Passing a value from a module
by qq (Hermit) on Apr 14, 2004 at 22:15 UTC

    You could just arrange your interface to return error messages as well as the 'cleaned' value.

    sub numbers { my ($class,$number) = @_; if ( /(\d+)/ ) { return ( $1, { msg => 'ok' } ); } else { return ( undef, { msg => 'numbers, please' } ); } } ... my ($age,$stuff) = Validate->numbers( ... ); unless ( $age ) { push @errors, $stuff->{msg}; }

    Also, I know this is just example code, but your regex to validate numbers isn't very solid. You'd be much better off with Regexp::Common.

    qq

      qq thanks for your reply. It worked wonderfully. In fact, I adapted it to have better messages:
      sub numbers { my ($class, $value, $len) = @_; if (!$value) { return (undef, { msg => 'cannot be blank.' }); } elsif (length($value) > $len ) { return (undef, { msg => 'has too many characters.' }); } elsif ($value !~ /^([0-9 \.-]*)$/) { return (undef, { msg => 'can only use numbers.' }); } else { return ("$1", { msg => '' }); } }

      —Brad
      "A little yeast leavens the whole dough."

        Careful about your tests, though. 0 isn't true, so you'll get the "cannot be blank" message. And /^([0-9 \.-]*)$/ would match '.-.. ', '--0.0.3....' , ' - ', etc.

        qq