Return value handling from Perl Package/Module

Viewed 201

I have written a perl package Parsepw which will parse the configuration file and returns the userid and password from config file to main script.

My module looks like this:

package Parsepw;

use strict;
use warnings;

use Exporter qw(import);

our @EXPORT_OK = qw(parse_config_file);

sub parse_config_file {
    ...
    #reads the config file, if not found print error message here else using regex extract user and password line
    ...
    if ( $user && $ciphertext ){
        return ( $user, $ciphertext );
    }
}
1;

Below is my main script:

...
use Parsepw;

my ($user, $passwd) = Parsepw::parse_config_file();
print "$user, $passwd\n";

I am able to retrieve user and passwd in positive test scenario, but in negative test case how can I handle it.

If suppose parse_config_file subroutine couldn't able to parse the configuration file in the module how can I throw an error/warning to my main script? Since the return value I have is $user, $passwd, how can I have the error/warning message?

Without using package everything works fine if I put all the code in single script. I was trying to write separate package for first time.

2 Answers

You could return nothing (zero scalars) when there's an error.

sub parse_config_file {
    ...

    if ( $user && $ciphertext ) {
        return ( $user, $ciphertext );
    } else {
        return;
    }
}

A list assignment in scalar returns the number of scalars returned by its right-hand side. This allows you to use

my ($user, $password) = parse_config_file(...)
   or die(...);

...

or

if ( my ($user, $password) = parse_config_file(...) ) {
   # Success
   ...
} else {
   # Error
   ...
}

You could throw an exception on error.

use Carp qw( croak );

sub parse_config_file {
    ...

    $user && $ciphertext
       or croak("...");

    return ( $user, $ciphertext );
}

You have various options to design the part of your module's interface that deals with errors.

One way, as ikegami says, could be to use a bare return, and let the module's user to check the results from parse_config_file.


If you want to "throw an exception", Perl offers a mechanism to do that using die passing blessed references, but you'll have to build a hierarchy of exception classes yourself (other languages provide a built hierarchy).

The example starts with the code for Foo.pm. It defines a minimal hierarchy of exception classes (Foo::Exception and Foo::Exception::ParseError).

As @ikegami pointed out, it's good practice to overload stringification: the reason is explained in the documentation for die:

Because Perl stringifies uncaught exception messages before display, you'll probably want to overload stringification operations on exception objects. See overload for details about that.

In this case, I chose a minimalistic approach: strigifying an exception object will simply return its class name.

In the implementation for parse_config_file, we pass die an object to thrown a typed exception.

use strict;
use warnings;

package Foo::Exception;
use overload '""' => sub { ref(shift) };

sub new {
    return bless {}, shift;
}

package Foo::Exception::ParseError;
use base 'Foo::Exception';

### The rest of the package, as in the example
### in your original question

package Foo;

use Exporter qw(import);

our @EXPORT_OK = qw(parse_config_file);

sub parse_config_file {
    die Foo::Exception::ParseError->new();
}
1;

In the script that uses the library (foo.pl), we can use the usual eval … if (@$) … form to trap the exception and react according to its type. $@ contains in this case a blessed reference (in other words, an object) representing the exception: you could enrich the exception classes so that they can contain additional information about the state of the process when the error occurred.

use lib '.';

use Foo qw/parse_config_file/;

eval {
    parse_config_file();
};
if ($@) {
    if ($@->isa('Foo::Exception::ParseError')) {
        print "Error parsing file\n";
    }
    else {
        die "Unexpected error!";
    }
}

1;
Related