README.md

# StdResult

<!-- MDOC !-->

[![Hex.pm version](https://img.shields.io/hexpm/v/std_result.svg?style=flat)](https://hex.pm/packages/std_result)
[![Hex.pm license](https://img.shields.io/hexpm/l/std_result.svg?style=flat)](https://hex.pm/packages/std_result)
[![Build Status](https://github.com/ImNotAVirus/std_result/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ImNotAVirus/std_result/actions/workflows/ci.yml)
[![Coverage Status](https://coveralls.io/repos/github/ImNotAVirus/std_result/badge.svg?branch=main)](https://coveralls.io/github/ImNotAVirus/std_result?branch=main)

## Table of Contents

* [Description](#description)
* [Installation](#installation)
* [The original issue](#the-original-issue)
* [Usage](#usage)
* [Contributing](#contributing)

## Description

`StdResult` is a library heavily inspired by Rust's `Result` type for handling 
function results in a consistent manner in Elixir.

Handling function results in Elixir can sometimes be inconsistent, with some 
functions returning `:ok` or `:error`, while others return tuples like 
`{:ok, term}` or `{:error, reason}`.

`StdResult` provides macros and functions to create and manipulate results,
promoting a unified approach to error handling.

Detailed documentation can be found at [https://hexdocs.pm/std_result](https://hexdocs.pm/std_result).

## Installation

Add `std_result` to your list of dependencies in mix.exs:

```elixir
def deps do
  [
    {:std_result, "~> 0.1"}
  ]
end
```

And that's all.

## The original issue

Let's take a simple example.  
Let's say we need to retrieve an environment variable, convert it to an integer 
and check that it's positive. Our function should return `{:ok, value}` or 
`{:error, reason}`.

One of the most popular solutions is to use `with` which would give something like: 

```elixir
with {:ok, port_str} <- System.fetch_env("PORT"),
     port when port > 0 <- String.to_integer(port_str) do
  {:ok, port}
else
  # Return by System.fetch_env/1
  :error -> {:error, "PORT env required"}
  # Returned by `port when port > 0`
  value when is_integer(value) -> {:error, "PORT must be a positive number, got: #{value}"}
end
```

I think you're beginning to understand what I'm getting at.  
Here our error handling is very complicated to reread because the returns of the 
functions used in the `with` are not normalized.

This is a simple example, but the more conditions there are, the more confusing 
the `else` block becomes.

The simplest solution would be to wrap each function in another, normalizing the 
returns. But you'd soon find yourself with lots of functions that just normalize 
each other's returns.

`StdResult` overcomes this problem.

## Usage

Here is the same problem as above, but with `StdResult` :

```elixir
import StdResult

System.fetch_env("PORT")
# This will transform `:error` into a `:error` tuple
|> normalize_result()
# If there is an error, explicit the message
|> or_result(err("PORT env required"))
# If no error, parse the string as an integer
# We could also have used `Integer.parse/1` but for simplicity's sake we won't.
|> map(&String.to_integer/1)
# Test if the number is positive
|> and_then(&(if &1 >= 0, do: ok(&1), else: err("PORT must be a positive number, got: #{&1}")))

# The result will be either:
# - `{:ok, port}`
# - `{:error, "PORT env required"}`
# - `{:error, "PORT must be a positive number, got: <value>"}`
```

**NOTE**: This lib is not intended to replace `with`, but rather to complement it in certain cases.

# Contributing

Contributions are welcome and appreciated. If you have any ideas, suggestions, or bugs to report,
please open an issue or a pull request on GitHub.