Foundation.Utils (foundation v0.1.0)

Pure utility functions for the Foundation layer.

Contains helper functions that are used across the Foundation layer. All functions are pure and have no side effects.

Summary

Functions

Convert atom keys to string keys recursively.

Check if a term is blank (nil, empty string, empty list, etc.).

Merge two maps recursively.

Calculate the deep size of a term.

Format byte size into human-readable string.

Format duration in nanoseconds to human readable string.

Generate a correlation ID string in UUID v4 format.

Generate a unique ID using monotonic time and randomness.

Get a nested value from a map with a default.

Measure execution time of a function in microseconds.

Measure memory consumption before and after a function execution.

Get monotonic timestamp in milliseconds.

Check if a term is present (not blank).

Returns process statistics for the current process.

Put a nested value in a map.

Retry a function with exponential backoff.

Safely convert a term to string representation.

Sanitize a string for safe logging/display.

Convert atom keys to string keys recursively.

Returns system statistics.

Truncate data if it's too large for storage.

Truncate data if it's too large for storage with custom size limit.

Check if a value is a valid positive integer.

Get current wall clock timestamp.

Functions

atomize_keys(map)

@spec atomize_keys(map()) :: map()

Convert atom keys to string keys recursively.

Examples

iex> data = %{key: %{nested: "value"}}
iex> Foundation.Utils.atomize_keys(data)
%{key: %{nested: "value"}}

blank?(str)

@spec blank?(term()) :: boolean()

Check if a term is blank (nil, empty string, empty list, etc.).

Examples

iex> Foundation.Utils.blank?(nil)
true

iex> Foundation.Utils.blank?("")
true

iex> Foundation.Utils.blank?("hello")
false

deep_merge(left, right)

@spec deep_merge(map(), map()) :: map()

Merge two maps recursively.

Examples

iex> map1 = %{a: %{b: 1}, c: 2}
iex> map2 = %{a: %{d: 3}, e: 4}
iex> Foundation.Utils.deep_merge(map1, map2)
%{a: %{b: 1, d: 3}, c: 2, e: 4}

deep_size(term)

@spec deep_size(term()) :: non_neg_integer()

Calculate the deep size of a term.

Examples

iex> size = Foundation.Utils.deep_size(%{a: 1, b: [1, 2, 3]})
iex> is_integer(size) and size > 0
true

format_bytes(bytes)

@spec format_bytes(non_neg_integer()) :: String.t()

Format byte size into human-readable string.

Examples

iex> Foundation.Utils.format_bytes(1024)
"1.0 KB"

iex> Foundation.Utils.format_bytes(1536)
"1.5 KB"

iex> Foundation.Utils.format_bytes(1048576)
"1.0 MB"

format_duration(nanoseconds)

@spec format_duration(non_neg_integer()) :: String.t()

Format duration in nanoseconds to human readable string.

Examples

iex> Foundation.Utils.format_duration(1_500_000_000)
"1.5s"

iex> Foundation.Utils.format_duration(2_500_000)
"2.5ms"

generate_correlation_id()

@spec generate_correlation_id() :: String.t()

Generate a correlation ID string in UUID v4 format.

Examples

iex> correlation_id = Foundation.Utils.generate_correlation_id()
iex> String.length(correlation_id)
36
iex> String.match?(correlation_id, ~r/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
true

generate_id()

@spec generate_id() :: pos_integer()

Generate a unique ID using monotonic time and randomness.

Examples

iex> id1 = Foundation.Utils.generate_id()
iex> id2 = Foundation.Utils.generate_id()
iex> id1 != id2
true

get_nested(map, path, default \\ nil)

@spec get_nested(map(), [atom()], term()) :: term()

Get a nested value from a map with a default.

Examples

iex> data = %{a: %{b: %{c: 42}}}
iex> Foundation.Utils.get_nested(data, [:a, :b, :c], 0)
42

iex> Foundation.Utils.get_nested(data, [:x, :y], "default")
"default"

measure(func)

@spec measure((-> result)) :: {result, non_neg_integer()} when result: any()

Measure execution time of a function in microseconds.

Examples

iex> {result, duration} = Foundation.Utils.measure(fn -> :timer.sleep(10); :ok end)
iex> result
:ok
iex> duration > 10_000  # At least 10ms in microseconds
true

measure_memory(func)

@spec measure_memory((-> result)) ::
  {result, {non_neg_integer(), non_neg_integer(), integer()}}
when result: any()

Measure memory consumption before and after a function execution.

Examples

iex> {result, {before, after, diff}} = Foundation.Utils.measure_memory(fn -> "test" end)
iex> result
"test"
iex> is_integer(before) and is_integer(after) and is_integer(diff)
true

monotonic_timestamp()

@spec monotonic_timestamp() :: integer()

Get monotonic timestamp in milliseconds.

Examples

iex> timestamp = Foundation.Utils.monotonic_timestamp()
iex> is_integer(timestamp)
true

present?(term)

@spec present?(term()) :: boolean()

Check if a term is present (not blank).

Examples

iex> Foundation.Utils.present?("hello")
true

iex> Foundation.Utils.present?(nil)
false

process_stats()

@spec process_stats() :: %{
  garbage_collection: any(),
  memory: any(),
  message_queue_len: any(),
  reductions: any(),
  status: any()
}

Returns process statistics for the current process.

Examples

iex> stats = Foundation.Utils.process_stats()
iex> Map.has_key?(stats, :memory)
true
iex> Map.has_key?(stats, :message_queue_len)
true

put_nested(map, list, value)

@spec put_nested(map(), [atom()], term()) :: map()

Put a nested value in a map.

Examples

iex> data = %{}
iex> Foundation.Utils.put_nested(data, [:a, :b, :c], 42)
%{a: %{b: %{c: 42}}}

retry(fun, opts \\ [])

@spec retry((-> any()), Keyword.t()) ::
  {:error, :max_attempts_exceeded} | {:ok, any()}

Retry a function with exponential backoff.

Examples

iex> result = Foundation.Utils.retry(fn -> :ok end, max_attempts: 3)
{:ok, :ok}

iex> result = Foundation.Utils.retry(fn -> {:error, :failed} end, max_attempts: 2)
{:error, :max_attempts_exceeded}

safe_inspect(term)

@spec safe_inspect(term()) :: String.t()

Safely convert a term to string representation.

Examples

iex> Foundation.Utils.safe_inspect(%{key: "value"})
"%{key: "value"}"

iex> Foundation.Utils.safe_inspect(:atom)
":atom"

sanitize_string(str)

@spec sanitize_string(String.t()) :: String.t()

Sanitize a string for safe logging/display.

Examples

iex> Foundation.Utils.sanitize_string("hello\nworld\ttab")
"hello world tab"

stringify_keys(map)

@spec stringify_keys(map()) :: map()

Convert atom keys to string keys recursively.

Examples

iex> data = %{key: %{nested: "value"}}
iex> Foundation.Utils.stringify_keys(data)
%{"key" => %{"nested" => "value"}}

system_stats()

@spec system_stats() :: %{
  atom_count: any(),
  memory: [
    {:atom
     | :atom_used
     | :binary
     | :code
     | :ets
     | :processes
     | :processes_used
     | :system
     | :total, non_neg_integer()},
    ...
  ],
  process_count: non_neg_integer(),
  scheduler_count: pos_integer(),
  scheduler_online: pos_integer()
}

Returns system statistics.

Examples

iex> stats = Foundation.Utils.system_stats()
iex> Map.has_key?(stats, :process_count)
true
iex> Map.has_key?(stats, :memory)
true

truncate_if_large(data)

@spec truncate_if_large(term()) :: term()

Truncate data if it's too large for storage.

Examples

iex> small_data = [1, 2, 3]
iex> Foundation.Utils.truncate_if_large(small_data)
[1, 2, 3]

iex> large_data = String.duplicate("x", 100_000)
iex> result = Foundation.Utils.truncate_if_large(large_data)
iex> is_map(result) and Map.has_key?(result, :truncated)
true

truncate_if_large(data, max_size)

@spec truncate_if_large(term(), pos_integer()) :: term()

Truncate data if it's too large for storage with custom size limit.

Examples

iex> small_data = [1, 2, 3]
iex> Foundation.Utils.truncate_if_large(small_data, 1000)
[1, 2, 3]

iex> large_data = String.duplicate("x", 2000)
iex> result = Foundation.Utils.truncate_if_large(large_data, 1000)
iex> is_map(result) and Map.has_key?(result, :truncated)
true

valid_positive_integer?(value)

@spec valid_positive_integer?(term()) :: boolean()

Check if a value is a valid positive integer.

Examples

iex> Foundation.Utils.valid_positive_integer?(42)
true

iex> Foundation.Utils.valid_positive_integer?(0)
false

iex> Foundation.Utils.valid_positive_integer?(-1)
false

iex> Foundation.Utils.valid_positive_integer?("42")
false

wall_timestamp()

@spec wall_timestamp() :: integer()

Get current wall clock timestamp.

Examples

iex> timestamp = Foundation.Utils.wall_timestamp()
iex> is_integer(timestamp)
true