=head1 NAME

Process::Results - standardized structure for returning results of a process

=head1 SYNOPSIS

 use Process::Results;
 
 my $results = Process::Results->new();
 
 some_subroutine(results=>$results) {
    ...
 }
 
 if ($results->success) {}
 else {}
 
 much more...

=head1 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 B<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 C<errors> means that the process failed, so
C<< $results-E<gt>success() >> returns false. A complete explanation of the
structure of a results object is in the next section.

=head2 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 C<success> and C<errors> properties are redundant: the presence of any
errors indicates failure. If both properties are present, C<success> overrides
C<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.

=head2 Message objects

Each message is a hash reference. Each message object must have the C<id>
property. Other properties can provide details about the message, for example
a problematic input param. You can create message objects with the
C<error()>, C<warning()>, and C<note()> methods:

 $results->error('file-not-found');
 $results->warning('very-long-loop');
 $results->warning('new-id');

More on those details below.

=head1 METHODS

=head2 new()

C<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

B<Options>

=over 4

=item * success

The C<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

=item * 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"
      }
   ]
 }

=item * results

C<new()> can return an existing results object if the C<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 C<results> object is sent, all other options are ignored.

=back

=head2 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 C<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;

=head2 success()

C<< $results-E<gt>success() >> returns true or false to indicate the success state of
the process. Success is determined in one of two ways: if the C<success>
property is defined, then the boolean value of that property is returned.
Else, if there are any messages in the C<errors> array, then false is returned,
else true is returned. C<success()> always returns either 1 or 0.

Here are some examples of some results objects and what C<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'}
   ],
 }

=head2 failure()

C<< $results-E<gt>failure() >> simply returns the boolean opposite of
C<< $results-E<gt>success() >>. C<< $results-E<gt>failure() >> always returns 1 or 0.

=head2 succeed(), fail()

C<< $results-E<gt>succeed() >> and C<< $results-E<gt>fail() >> explicitly set the
success state of the results object. All they do is set the C<success>
property to 1 (C<succeed>) or 0 (C<fail>).

=head2 unsucceed(), unfail()

C<< $results-E<gt>unsucceed() >> and C<< $results-E<gt>unfail() >> do the same thing:
delete the C<success> proeperty.

=head2 json()

C<< $results-E<gt>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 C<success> property is set to the JSON
value of C<true> or C<false>. Other then that, nothing complicated.

=head1 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.

=head1 AUTHOR

Miko O'Sullivan
F<miko@idocs.com>

=head1 VERSION

Version: 0.01

=head1 HISTORY

=over 4

=item * Version 0.01 Aug 9, 2016

Initial release.

=back

