Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions lib/Result/Simple.pm
Original file line number Diff line number Diff line change
Expand Up @@ -144,25 +144,34 @@ sub wrap_code {

# `chain` takes a function name and a result tuple (T, E) and returns a new result tuple (T, E).
sub chain {
my ($f, $value, $error) = @_;
my ($function, $value, $error) = @_;

if (CHECK_ENABLED) {
croak "`chain` must be called in list context" unless wantarray;
croak "`chain` arguments must be func and result like (func, T, E)" unless @_ == 3;
}

my $code = ref $f ? $f : do { my $t = caller(0); $t->can($f) or croak "Function `$f` not found in $t" };
my $code = ref $function ? $function : do {
my $caller = caller(0);
$caller->can($function) or croak "Function `$function` not found in $caller";
};
return err($error) if $error;
return $code->($value);
}

# `pipeline` takes a list of function names and returns a new function.
sub pipeline {
my (@f) = @_;
my (@functions) = @_;

my @codes = map { ref $_ ? $_ : do { my $t = caller(0); $t->can($_) or croak "Function `$_` not found in $t" } } @f;
my @codes = map {
my $f = $_;
ref $f ? $f : do {
my $caller = caller(0);
$caller->can($f) or croak "Function `$f` not found in $caller";
};
} @functions;

sub {
my $pipelined = sub {
my ($value, $error) = @_;

if (CHECK_ENABLED) {
Expand All @@ -176,7 +185,13 @@ sub pipeline {
return err($error) if $error;
}
return ok($value);
}
};

my ($package, $file, $line) = caller(0);
my $fullname = "$package\::__PIPELINED_FUNCTION__";
Sub::Util::set_subname($fullname, $pipelined);

return $pipelined;
}

# `unsafe_nwrap` takes a Result<T, E> and returns a T when the result is an Ok, otherwise it throws exception.
Expand Down
59 changes: 53 additions & 6 deletions t/Result-Simple.t
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Test the Result::Simple module with CHECK_ENABLED is truthy.

=cut

use Test2::V0 qw(subtest is like unlike dies done_testing);
use Test2::V0 qw(subtest is like unlike dies note done_testing);

use lib "t/lib";
use TestType qw( Int NonEmptyStr );
Expand Down Expand Up @@ -111,17 +111,23 @@ subtest 'Test `result_for` function' => sub {
};

subtest 'Test the details of `retsult_for` function' => sub {
subtest 'Useful stacktrace' => sub {

subtest 'stacktrace' => sub {
result_for test_stacktrace => Int, NonEmptyStr;
sub test_stacktrace { Carp::confess('hello') }

local $@;
eval { my ($data, $err) = test_stacktrace() };
my $error = $@;
my @errors = split /\n/, $error;

my $file = __FILE__;
like $@, qr!hello at $file line!;
like $@, qr/main::test_stacktrace\(\) called at $file line /, 'stacktrace includes function name';
unlike $@, qr/Result::Simple::/, 'stacktrace does not include Result::Simple by Scope::Upper';
my $line = __LINE__;

like $errors[0], qr!hello at $file line @{[$line - 8]}!;
like $errors[1], qr!test_stacktrace\(\) called at $file line @{[$line - 5]}!, 'stacktrace includes function name';
unlike $error, qr!Result/Simple.pm!, 'stacktrace does not include Result::Simple';
note $errors[0];
note $errors[1];
};

subtest 'Same subname and prototype as original' => sub {
Expand Down Expand Up @@ -188,6 +194,26 @@ subtest 'Test `chain` function' => sub {
like dies { my $v = chain(chain_test => 1, 2) }, qr/`chain` must be called in list context/;
like dies { my ($v, $e) = chain(chain_test => 1) }, qr/`chain` arguments must be func and result/;
like dies { my ($v, $e) = chain(unknown => 1, 2) }, qr/Function `unknown` not found in main/;

subtest 'stacktrace' => sub {
sub chain_stacktrace { Carp::confess('hello') }

local $@;
eval { my ($v, $e) = chain(chain_stacktrace => ok(8)) };
my $error = $@;
my @errors = split /\n/, $error;

my $file = __FILE__;
my $line = __LINE__;

like $errors[0], qr!hello at $file line @{[$line - 8]}!, 'Throw an exception at `chain_stacktrace` function';
like $errors[1], qr!chain_stacktrace\(8\) called at .+/Result/Simple.pm!;
like $errors[2], qr!chain\(["']chain_stacktrace["'], 8, undef\) called at $file line @{[$line - 5]}!;

note $errors[0];
note $errors[1];
note $errors[2];
}
};

subtest 'Test `pipeline` function' => sub {
Expand All @@ -213,6 +239,27 @@ subtest 'Test `pipeline` function' => sub {
like dies { my $v = $code->(1, 2) }, qr/pipelined function must be called in list context/;
like dies { my ($v, $e) = $code->(1) }, qr/pipelined function arguments must be result/;
like dies { my $c = pipeline qw( unknown ) }, qr/Function `unknown` not found in main/;

subtest 'stacktrace' => sub {
sub pipeline_stacktrace { Carp::confess('hello') }

my $pipelined = pipeline qw( pipeline_test pipeline_stacktrace );

local $@;
eval { my ($v, $e) = $pipelined->(ok(8)) };
my $error = $@;
my @errors = split /\n/, $error;

my $file = __FILE__;
my $line = __LINE__;

like $errors[0], qr!hello at $file line @{[$line - 10]}!, 'Throw an exception at `pline_stacktrace` function';
like $errors[1], qr!pipeline_stacktrace\(4\) called!;
like $errors[2], qr!__PIPELINED_FUNCTION__\(8, undef\) called at $file line @{[$line - 5]}!;
note $errors[0];
note $errors[1];
note $errors[2];
}
};

done_testing;
17 changes: 11 additions & 6 deletions t/check-disabled.t
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ These tests are same cases as Result-Simple.t, but CHECK_ENABLED is falsy.

=cut

use Test2::V0 qw(subtest is like unlike lives done_testing);
use Test2::V0 qw(subtest is like unlike lives dies note done_testing);
use Test2::V0 ok => { -as => 'test_ok' };

use lib "t/lib";
Expand Down Expand Up @@ -83,16 +83,21 @@ subtest 'Test `result_for` function' => sub {
subtest 'Test the details of `retsult_for` function' => sub {
# 'When CHECK_ENABLED is falsy, then do not wrap the original function';

subtest 'Useful stacktrace' => sub {
subtest 'stacktrace' => sub {
result_for test_stacktrace => Int, NonEmptyStr;
sub test_stacktrace { Carp::confess('hello') }

eval { my ($data, $err) = test_stacktrace() };
my $error = dies { my ($data, $err) = test_stacktrace() };
my @errors = split /\n/, $error;

my $file = __FILE__;
like $@, qr!hello at $file line!;
like $@, qr/main::test_stacktrace\(\) called at $file line /, 'stacktrace includes function name';
unlike $@, qr/Result::Simple::/, 'stacktrace does not include Result::Simple by Scope::Upper';
my $line = __LINE__;

like $errors[0], qr!hello at $file line @{[$line - 6]}!;
like $errors[1], qr!test_stacktrace\(\) called at $file line @{[$line - 4]}!, 'stacktrace includes function name';
unlike $error, qr!Result/Simple.pm!, 'stacktrace does not include Result::Simple';
note $errors[0];
note $errors[1];
};

subtest 'Same subname and prototype as original' => sub {
Expand Down