NAME Process::Results - standardized structure for returning results of a process SYNOPSIS use Process::Results; my $results = Process::Results->new(); some_subroutine(results=>$results) { ... } if ($results->success) {} else {} more... OVERVIEW Getting the details about the results of a subroutine call can be challenging. It's easy enough for a subroutine to indicate if it succeeded or not, or to simply die or croak. Communicating more detail, however, can get complicated. What was the cause of the failure? What was the input value that caused it? Maybe there were multiple problems, any of which could have independently caused a failure. Furthermore, it's not just failures that need communicating. Maybe there were results of the process that need to be communicated back to the caller, in addition to the success or failure of the operation. Process::Results provides a standardized way for caller and subroutine to communicate complex details of an operation. A Process::Results object is passed into the subroutine call, which can then store results information in the object. The sub doesn't even have to return the object because the caller still has a reference to it. Keep in mind that a process doesn't have to return the results object, so your sub can still return success, failure, or some other value without the caller having to check the Results object. In many cases, a successful process doesn't need to provide any details - it's only on failure that details are needed. At its simplest, a Results object is just an empty hash. By default, an empty hash indicates success, which can be checked with the success method: $results->success() If you prefer, you can check for failure, which just returns the opposite of success(): $results->failure() If you prefer that the results object defaults to false, just add a 'success' option when creating the new object: $results = Process::Results->new(success=>0); $results->success(); # returns false In a more complex situation, the results object might contain one or more messages in the errors array. Such an object would look like this: { errors => [ { id=>'file-open-error', path=>'/tmp/output.txt' }, { id=>'missing-param', param_name=>'email' }, ] } The presence of any elements in errors means that the process failed, so $results->success() returns false. A complete explanation of the structure of a results object is in the next section. Structure A complete structure of a results object looks like this: { success => 0, errors => [ { id=>'file-open-error', path=>'/tmp/output.txt' }, { id=>'missing-param', param_name=>'email' }, ], warnings => [ # more messages here ], notes => [ # more messages here ], details => { # a hash that can contain anything you want } } The success and errors properties are redundant: the presence of any errors indicates failure. If both properties are present, success overrides errors. Errors indicate that the process failed. Warnings do not indicate a failure, but do indicate that something went wrong. Notes are simply information about the process and don't mean anything was wrong at all. Message objects Each message is a hash reference. Each message object must have the id property. Other properties can provide details about the message, for example a problematic input param. You can create message objects with the error(), warning(), and note() methods: $results->error('file-not-found'); $results->warning('very-long-loop'); $results->warning('new-id'); More on those details below. METHODS new() Process::Results-new()> creates a new Process::Results object. By default, the object is an empty hash. my $results = Process::Results->new(); # returns empty, blessed hashref Options * success The success option sets an explicit success or failure for the new object. By default, you might want your results object to fail by default. In that case you could do the following: $results = Process::Results->new(success=>0); # stuff happens, but nothing affects the results object $results->success(); # returns false * json You can pass in a json string which will be parsed and used to populate the new object. For example: $results = Process::Results->new(json=>'{"errors":[{"id":"no-file"}]}'); produces this structure: { errors => [ { id => "no-file" } ] } * results new() can return an existing results object if the results option is sent. This option is handy when you want to ensure that your subroutine has a results object regardless of whether or not one was passed in. For example, consider the following sub: sub mysub { my ($param, %opts) = @_; my $results = Process::Results->new(results=>$opts{'results'}); # [do stuff] } In that example, the caller can send in a results object with the options hash. If it does so, that result object is used. If no such option is sent, the sub has a new results object to use. If the results object is sent, all other options are ignored. error(), warning(), note() Each of these methods creates a message object (which is just a hashref) for their respective category. The single required param is an id for the message. The id can be any defined string that you want. For example, the following code creates an error object with the id "do-not-find-file". $results->error('do-not-find-file'); That code creates a message object, stored in the errors array, with the following structure: { 'id' => 'do-not-find-file' } A message object can hold any other properties you want. Those properties should give the details of the message. Those properties can be set with additional params to the method call. So, for example, the following code sets an error with the id "do-not-find-file", along with indicating the path that does not have the file: $results->error('do-not-find-file', path=>$file_path); which would result in an object like this: { 'id' => 'do-not-find-file', 'path' => '/tmp/data.txt' } The message method returns the message object, so if you prefer you can set those properties directly in the message object, like this: $msg = $results->error('do-not-find-file'); $msg->{'path'} = $file_path; success() $results->success() returns true or false to indicate the success state of the process. Success is determined in one of two ways: if the success property is defined, then the boolean value of that property is returned. Else, if there are any messages in the errors array, then false is returned, else true is returned. success() always returns either 1 or 0. Here are some examples of some results objects and what success() returns: # empty hash returns true {} # defined, false value of the success property returns false { 'success'=>0 } # errors array with at least one message returns false { 'errors'=>[ {'id'=>'do-not-find-file'} ], } # If there is a conflict between explicit success and the errors array, then # the explicit success is returned. That's confusing, so try to avoid that. { 'success'=>1, 'errors'=>[ {'id'=>'do-not-find-file'} ], } failure() $results->failure() simply returns the boolean opposite of $results->success(). $results->failure() always returns 1 or 0. succeed(), fail() $results->succeed() and $results->fail() explicitly set the success state of the results object. All they do is set the success property to 1 (succeed) or 0 (fail). unsucceed(), unfail() $results->unsucceed() and $results->unfail() do the same thing: delete the success proeperty. json() $results->json() returns a JSON representation of the results object. That's all, it takes no params, it just returns a JSON string. OK, one minor thing to note is that the success property is set to the JSON value of true or false. Other then that, nothing complicated. TERMS AND CONDITIONS Copyright (c) 2016 by Miko O'Sullivan. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. This software comes with NO WARRANTY of any kind. AUTHOR Miko O'Sullivan miko@idocs.com VERSION Version: 0.01 HISTORY * Version 0.01 Aug 9, 2016 Initial release. * Version 0.02 Aug 15, 2016 Adding Process::Results::Holder to Process::Results.