Packages

ExEssentials is a powerful utility library for Elixir that serves as a true toolbox — bringing together a collection of generic, reusable, and ready-to-use helpers to accelerate Elixir application development.

Current section

Files

Jump to
ex_essentials README.md
Raw

README.md

# ExEssentials

<p align="center">
  <picture>
    <source srcset="https://raw.githubusercontent.com/zander-br/ex_essentials/main/assets/ex_essentials.png">
    <img alt="ExEssentials logo" src="https://raw.githubusercontent.com/zander-br/ex_essentials/main/assets/ex_essentials.png" width="320">
  </picture>
</p>

<p align="center">
  <a href="https://hex.pm/packages/ex_essentials"><img src="https://img.shields.io/hexpm/v/ex_essentials.svg" alt="Hex version"></a>
  <a href="https://hexdocs.pm/ex_essentials"><img src="https://img.shields.io/badge/hex-docs-lightgrey.svg" alt="Hex docs"></a>
  <a href="https://github.com/zander-br/ex_essentials/blob/main/LICENSE.txt"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="License"></a>
</p>

**ExEssentials** is a powerful utility library for Elixir that serves as a true toolbox — bringing together a collection of generic, reusable, and ready-to-use helpers to accelerate Elixir application development.

Designed with a focus on productivity and organization, it helps you write cleaner, more maintainable code while saving valuable development time.

---

## Features

- 🛠 **Flow Builder (Runner)**: Model complex business logic as a sequence of named steps (sync or async) with built-in error recovery.
- 🔑 **Fingerprinting**: Generate deterministic SHA-256 hashes and compare maps or structs by selected keys.
- 🇧🇷 **Brazilian Document Validation**: Robust CPF and CNPJ (numeric & alphanumeric) validation and formatting.
- 🌐 **Web & API Helpers**: RFC 7807 compliant request validation, query parameter normalization, and service toggling.
- 📑 **XML Utilities**: Safe and sanitized XML building built on top of Saxy.
- 🗺 **Map Utilities**: Clean way to rename keys, compact `nil`, or blank values from maps.

---

## Installation

The package can be installed by adding `ex_essentials` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ex_essentials, "~> 0.11.0"}
  ]
end
```

---

## Usage

### 🚀 Core Runner (Flow Builder)

Inspired by `Ecto.Multi`, the `Runner` allows you to build fail-fast execution flows with named steps.

```elixir
alias ExEssentials.Core.Runner

runner =
  Runner.new(timeout: 5_000)
  |> Runner.put(:user_id, 123)
  |> Runner.run(:fetch_user, fn %{user_id: id} -> {:ok, %{id: id, name: "John"}} end)
  |> Runner.run_async(:send_email, fn _ -> {:ok, :sent} end)
  |> Runner.run(:log_action, fn %{fetch_user: user} -> {:ok, "Logged #{user.name}"} end)

case Runner.finish(runner) do
  {:ok, changes} ->
    IO.puts("User fetched: #{changes.fetch_user.name}")
  {:error, step, reason, changes_before} ->
    IO.inspect({step, reason}, label: "Flow failed")
end
```

### 🔑 Fingerprint Utilities

Generate SHA-256 fingerprint hashes and compare maps or structs based on a subset of keys.

```elixir
alias ExEssentials.Core.Fingerprint

# Generate hash for selected fields
user = %{id: 1, name: "Alice", email: "alice@example.com"}
Fingerprint.hash(user, [:id, :email])
# => "..."

# Compare two maps or structs by selected keys
user_a = %{id: 1, name: "Alice", role: :admin}
user_b = %{id: 1, name: "Alice", role: :user}

Fingerprint.equal?(user_a, user_b, [:id, :name]) # => true
Fingerprint.equal?(user_a, user_b, [:id, :role]) # => false
```

### 🇧🇷 Brazilian Document Validation

Validate, format, and mask CPF and CNPJ numbers effortlessly. Supports alphanumeric CNPJs and automatic digit extraction.

#### Validation & Formatting

```elixir
alias ExEssentials.BrazilianDocument.Validator
alias ExEssentials.BrazilianDocument.Formatter

# Validate CPF or CNPJ (Numeric or Alphanumeric)
Validator.valid?("123.456.789-00") # => true
Validator.valid?("12ABC34501DE35") # => true (New Alphanumeric CNPJ)

# Format for display
Formatter.format("39053344705")      # => "390.533.447-05"
Formatter.format("11222333000181")   # => "11.222.333/0001-81"

# Mask sensitive data
Formatter.mask("44286185060")        # => "***.861.850-**"
Formatter.mask("20495056000171")     # => "20.***.***/0001-7*"
```

#### Ecto Integration

Easily validate documents in your changesets.

```elixir
defmodule MyApp.User do
  use Ecto.Schema
  import Ecto.Changeset
  import ExEssentials.BrazilianDocument.Changeset

  schema "users" do
    field :document, :string
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:document])
    |> validate_brazilian_document(:document) # Validates CPF or CNPJ
    # Or specifically:
    # |> validate_cpf(:document)
    # |> validate_cnpj(:document)
  end
end
```

### 🛡 Web Utilities (Plugs & Validators)

#### Request Validation

A `Plug` for validating request parameters using `Ecto.Changeset`. If validation fails, it returns a `400 Bad Request` following [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807).

```elixir
# 1. Define your validator (mapping actions to changesets)
defmodule MyAppWeb.Validators.User do
  import Ecto.Changeset

  def create(params) do
    {%{}, %{name: :string, email: :string}}
    |> cast(params, [:name, :email])
    |> validate_required([:name, :email])
  end
end

# 2. Use the plug in your controller
defmodule MyAppWeb.UserController do
  use MyAppWeb, :controller
  plug ExEssentials.Web.Plugs.RequestValidator, validator: MyAppWeb.Validators.User

  def create(conn, params) do
    # Only executes if params are valid
    text(conn, "User Created")
  end
end
```

#### Query Parameter Normalization

Automatically converts query string values (like `"true"`, `"null"`, `"123"`, or lists `"1,2,3"`) into their Elixir types.

```elixir
# In your endpoint.ex or router pipeline
plug ExEssentials.Web.Plugs.NormalizeQueryParams

# Input:  /api/search?active=true&tags=elixir,rust&limit=10
# Output: %{"active" => true, "tags" => ["elixir", "rust"], "limit" => 10}
```

#### Feature Flag Route Disabling

Disable specific controller actions based on feature flags (supports `FunWithFlags`).

```elixir
# In your controller
plug ExEssentials.Web.Plugs.DisableServices,
  disabled_actions: [:delete, :update],
  flag_name: :maintenance_mode
```

### 📑 XML Utilities

A safe wrapper around `Saxy` to build XML documents with automatic sanitization.

```elixir
import ExEssentials.XML

# Automatically escapes <, >, &, etc.
element_sanitize("note", [], ["Some <script> & unsafe content"])
# => {"note", [], ["Some &lt;script&gt; &amp; unsafe content"]}
```

### 🗺 Map Utilities

Helpers for transforming and cleaning up map structures.

```elixir
alias ExEssentials.Core.Map, as: MapUtil

# Renaming keys
map = %{name: "Alice", age: 30}
MapUtil.renake(map, [:name, age: :years]) 
# => %{name: "Alice", years: 30}

# Cleaning up maps (removing nil or blank values)
MapUtil.compact(%{a: 1, b: nil, c: "", d: [], e: %{}}) 
# => %{a: 1}
```

---

## Configuration

You can customize the `RequestValidator` response format:

```elixir
config :ex_essentials, :web_request_validator,
  json_library: Jason,
  error_code: :invalid_parameter,
  error_title: "Invalid request parameters"
```

---

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin feature/my-new-feature`)
5. Create a new Pull Request

---

## License

ExEssentials is released under the **Apache License 2.0**. See the [LICENSE.txt](LICENSE.txt) file for details.