[![Actions Status](https://github.com/kfly8/Result-Simple/actions/workflows/test.yml/badge.svg)](https://github.com/kfly8/Result-Simple/actions) [![Coverage Status](https://img.shields.io/coveralls/kfly8/Result-Simple/main.svg?style=flat)](https://coveralls.io/r/kfly8/Result-Simple?branch=main) [![MetaCPAN Release](https://badge.fury.io/pl/Result-Simple.svg)](https://metacpan.org/release/Result-Simple)
# NAME

Result::Simple - A dead simple perl-ish Result like F#, Rust, Go, etc.

# SYNOPSIS

```perl
# Enable type check. The default is false.
BEGIN { $ENV{RESULT_SIMPLE_CHECK_ENABLED} = 1 }

use Test2::V0;
use Result::Simple;
use Types::Common -types;

use kura ErrorMessage => StrLength[3,];
use kura ValidName    => sub { my (undef, $e) = validate_name($_); !$e };
use kura ValidAge     => sub { my (undef, $e) = validate_age($_); !$e };
use kura ValidUser    => Dict[name => ValidName, age => ValidAge];

sub validate_name {
    my $name = shift;
    return Err('No name') unless defined $name;
    return Err('Empty name') unless length $name;
    return Err('Reserved name') if $name eq 'root';
    return Ok($name);
}

sub validate_age {
    my $age = shift;
    return Err('No age') unless defined $age;
    return Err('Invalid age') unless $age =~ /\A\d+\z/;
    return Err('Too young age') if $age < 18;
    return Ok($age);
}

sub new_user :Result(ValidUser, ArrayRef[ErrorMessage]) {
    my $args = shift;
    my @errors;

    my ($name, $name_err) = validate_name($args->{name});
    push @errors, $name_err if $name_err;

    my ($age, $age_err) = validate_age($args->{age});
    push @errors, $age_err if $age_err;

    return Err(\@errors) if @errors;
    return Ok({ name => $name, age => $age });
}

my ($user1, $err1) = new_user({ name => 'taro', age => 42 });
is $user1, { name => 'taro', age => 42 };
is $err1, undef;

my ($user2, $err2) = new_user({ name => 'root', age => 1 });
is $user2, undef;
is $err2, ['Reserved name', 'Too young age'];
```

# DESCRIPTION

Result::Simple is a dead simple Perl-ish Result.

Result represents a function's return value as success or failure, enabling safer error handling and more effective control flow management.
This pattern is used in other languages such as F#, Rust, and Go.

In Perl, this pattern is also useful, and this module provides a simple way to use it.
This module does not wrap a return value in an object. Just return a tuple like `($data, undef)` or `(undef, $err)`.

## EXPORT FUNCTIONS

### Ok

```perl
Ok($data)
# => ($data, undef)
```

Return a tuple of a given value and undef. When the function succeeds, it should return this.

### Err

```perl
Err($err)
# => (undef, $err)
```

Return a tuple of undef and a given error. When the function fails, it should return this.
Note that the error value must be a truthy value, otherwise it will throw an exception.

## ATTRIBUTES

### :Result(T, E)

You can use the `:Result(T, E)` attribute to define a function that returns a success or failure and asserts the return value types. Here is an example:

```perl
sub half :Result(Int, ErrorMessage) ($n) {
    if ($n % 2) {
        return Err('Odd number');
    } else {
        return Ok($n / 2);
    }
}
```

- T (success type)

    When the function succeeds, then returns `($data, undef)`, and `$data` should satisfy this type.

- E (error type)

    When the function fails, then returns `(undef, $err)`, and `$err` should satisfy this type.
    Additionally, type E must be truthy value to distinguish between success and failure.

    ```perl
    sub foo :Result(Int, Str) ($input) { }
    # => throw exception: Result E should not allow falsy values: ["0"] because Str allows "0"
    ```

    When a function never returns an error, you can set type E to `undef`:

    ```perl
    sub double :Result(Int, undef) ($n) { Ok($n * 2) }
    ```

Note that types require `check` method that returns true or false. So you can use your favorite type constraint module like
[Type::Tiny](https://metacpan.org/pod/Type%3A%3ATiny), [Moose](https://metacpan.org/pod/Moose), [Mouse](https://metacpan.org/pod/Mouse) or [Data::Checks](https://metacpan.org/pod/Data%3A%3AChecks) etc.

## ENVIRONMENTS

### `$ENV{RESULT_SIMPLE_CHECK_ENABLED}`

If the `ENV{RESULT_SIMPLE_CHECK_ENABLED}` environment is truthy before loading this module, it works as an assertion.
Otherwise, if it is falsy, `:Result(T, E)` attribute does nothing. The default is false.

```perl
sub invalid :Result(Int, undef) { Ok("hello") }

my ($data, $err) = invalid();
# => throw exception when check enabled
# => no exception when check disabled
```

The following code is an example to enable it:

```perl
BEGIN { $ENV{RESULT_SIMPLE_CHECK_ENABLED} = is_test ? 1 : 0 }
use Result::Simple;
```

This option is useful for development and testing mode, and it recommended to set it to false for production.

# NOTE

## What happens when you forget to call `Ok` or `Err`?

Forgetting to call `Ok` or `Err` function is a common mistake. Consider the following example:

```perl
sub validate_name :Result(Str, ErrorMessage) ($name) {
    return "Empty name" unless $name; # Oops! Forgot to call `Err` function.
    return Ok($name);
}

my ($name, $err) = validate_name('');
# => throw exception: Invalid result tuple (T, E)
```

In this case, the function throws an exception because the return value is not a valid result tuple `($data, undef)` or `(undef, $err)`.
This is fortunate, as the mistake is detected immediately. The following case is not detected:

```perl
sub foo :Result(Str, ErrorMessage) {
    return (undef, 'apple'); # No use of `Ok` or `Err` function.
}

my ($data, $err) = foo;
# => $err is 'apple'
```

Here, the function returns a valid failure tuple `(undef, $err)`. However, it is unclear whether this was intentional or a mistake.
The lack of `Ok` or `Err` makes the intent ambiguous.

Conclusively, be sure to use `Ok` or `Err` functions to make it clear whether the success or failure is intentional.

# LICENSE

Copyright (C) kobaken.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

# AUTHOR

kobaken <kentafly88@gmail.com>