Current section
Files
Jump to
Current section
Files
lib/nex/utils.ex
defmodule Nex.Utils do
@moduledoc """
Shared utility functions used across the Nex framework.
This module contains common functions that are used by multiple
modules to avoid code duplication.
"""
@doc """
Safely converts a string to an existing atom.
Returns `{:ok, atom}` if the atom exists, or `:error` if it doesn't.
This prevents atom exhaustion attacks by only converting atoms that
have already been defined.
## Examples
iex> Nex.Utils.safe_to_existing_atom("Phoenix")
{:ok, Phoenix}
iex> Nex.Utils.safe_to_existing_atom("NonExistentModule")
:error
"""
@spec safe_to_existing_atom(String.t()) :: {:ok, atom()} | :error
def safe_to_existing_atom(string) do
{:ok, String.to_existing_atom(string)}
rescue
ArgumentError -> :error
end
@doc """
Normalizes a module name into a dotted Elixir module string.
## Examples
iex> Nex.Utils.normalize_module_name(MyApp.Pages.Index)
"MyApp.Pages.Index"
iex> Nex.Utils.normalize_module_name("Elixir.MyApp.Pages.Index")
"MyApp.Pages.Index"
"""
@spec normalize_module_name(module() | String.t()) :: String.t()
def normalize_module_name(module_name) when is_atom(module_name) do
module_name
|> Module.split()
|> Enum.join(".")
end
def normalize_module_name(module_name) when is_binary(module_name) do
String.trim_leading(module_name, "Elixir.")
end
@doc """
Safely converts a module name string to an existing module.
Returns `{:ok, module}` if the module is loaded, or `:error` if not.
## Examples
iex> Nex.Utils.safe_to_existing_module("Nex.Handler")
{:ok, Nex.Handler}
iex> Nex.Utils.safe_to_existing_module("Non.Existent")
:error
"""
@spec safe_to_existing_module(module() | String.t()) :: {:ok, module()} | :error
def safe_to_existing_module(module_name) do
normalized_name = normalize_module_name(module_name)
case safe_to_existing_atom("Elixir.#{normalized_name}") do
{:ok, module} ->
if Code.ensure_loaded?(module), do: {:ok, module}, else: :error
:error ->
:error
end
end
@doc """
Generates a cryptographically secure random token.
## Examples
iex> Nex.Utils.generate_token()
"abc123..."
"""
@spec generate_token(pos_integer()) :: String.t()
def generate_token(length \\ 24) do
length
|> :crypto.strong_rand_bytes()
|> Base.url_encode64(padding: false)
end
@doc """
Generates a cryptographically secure random hex string.
## Examples
iex> Nex.Utils.generate_hex(16)
"abc123..."
"""
@spec generate_hex(pos_integer()) :: String.t()
def generate_hex(length) do
:crypto.strong_rand_bytes(length) |> Base.encode16(case: :lower)
end
end