Skip to content

hb_util.erl - Core Utility Functions

Overview

Purpose: Collection of utility functions for HyperBEAM development
Module: hb_util
Pattern: Stateless helper functions for type coercion, encoding, data manipulation

This module provides a comprehensive set of utility functions including type coercion, ID encoding/decoding, deep data structure manipulation, list operations, statistical functions, and various helper utilities used throughout the HyperBEAM codebase.

Dependencies

  • External: b64fast (base64 encoding)
  • HyperBEAM: hb_opts, hb_maps, hb_ao, hb_path, hb_message, hb_features, hb_ao_device, hb_structured_fields
  • Erlang/OTP: crypto, base64, string, lists, maps

Public Functions Overview

Type Coercion

FunctionDescription
int/1Convert binary/string/integer to integer
float/1Convert binary/string/integer/float to float
atom/1Convert binary/string to existing atom
bin/1Convert atom/integer/float/string to binary
list/1Convert binary/atom to string list
map/1Convert key-value list to map

Encoding & IDs

FunctionDescription
encode/1URL-safe base64 encode binary
decode/1URL-safe base64 decode binary
safe_encode/1Safe encode (returns input if not binary)
safe_decode/1Safe decode returning {ok, Data} or {error, invalid}
id/1, id/2Get message ID (human-readable)
native_id/1Convert to 32-byte native ID
human_id/1Convert to 43-byte base64 human ID
to_hex/1Convert binary to lowercase hex string

Deep Data Operations

FunctionDescription
deep_get/3, deep_get/4Get nested value by path
deep_set/4Set nested value by path
deep_merge/3Recursively merge two maps
find_value/2, find_value/3Find value in map or key-value list
find_target_path/2Find route path for request message
template_matches/3Check if message matches template

List Operations

FunctionDescription
unique/1Remove duplicates preserving order
list_with/2Intersection of two lists
list_without/2Remove items from list
list_replace/3Replace item in list
to_sorted_list/1, to_sorted_list/2Sort map/list deterministically
to_sorted_keys/1, to_sorted_keys/2Get sorted keys from map/list
hd/1, hd/2, hd/3Get first element of numbered map

Numbered Message Operations

FunctionDescription
number/1Label list elements with numbers
list_to_numbered_message/1Convert list to numbered map
message_to_ordered_list/1, message_to_ordered_list/2Convert numbered map to list
numbered_keys_to_list/2Extract numbered keys as list
is_ordered_list/2Check if map is ordered list

String Operations

FunctionDescription
to_lower/1Convert string to lowercase
remove_common/2Remove common prefix from string
key_to_atom/1, key_to_atom/2Convert key to atom (with dash→underscore)
unquote/1Remove surrounding quotes
is_string_list/1Check if term is string (char list)
binary_to_strings/1Parse binary as structured field list

String Parsing

FunctionDescription
split_depth_string_aware/2Split honoring nesting and quotes
split_depth_string_aware_single/2Split single part with nesting
split_escaped_single/2Split respecting escape sequences

Math & Statistics

FunctionDescription
ceil_int/2Round up to nearest multiple
floor_int/2Round down to nearest multiple
count/2Count occurrences in list
mean/1Calculate arithmetic mean
variance/1Calculate variance
stddev/1Calculate standard deviation
weighted_random/1Pick random item by weight

Validation

FunctionDescription
check_size/2Check binary size against list or range
check_type/2Check value type
check_value/2Check value in allowed list

Error Handling

FunctionDescription
ok/1, ok/2Unwrap {ok, Value} or throw
ok_or_throw/3Assert condition or throw
maybe_throw/2Conditionally throw based on opts

Control Flow

FunctionDescription
until/1, until/2, until/3Wait for condition with optional work

Formatting

FunctionDescription
human_int/1Format integer with commas

Map Operations

FunctionDescription
lower_case_key_map/2Recursively lowercase map keys

Module Introspection

FunctionDescription
is_hb_module/1, is_hb_module/2Check if module is HyperBEAM module
all_hb_modules/0List all loaded HB modules
all_atoms/0List all atoms in VM
binary_is_atom/1Check if binary is existing atom

Public Functions

1. Type Coercion: int/1, float/1, atom/1, bin/1, list/1, map/1

-spec int(binary() | list() | integer()) -> integer().
-spec float(binary() | list() | float() | integer()) -> float().
-spec atom(binary() | list() | atom()) -> atom().
-spec bin(atom() | integer() | float() | list() | binary()) -> binary().
-spec list(binary() | list() | atom()) -> list().
-spec map(list() | map()) -> map().

Description: Type coercion functions for converting between common Erlang types.

Test Code:
-module(hb_util_type_test).
-include_lib("eunit/include/eunit.hrl").
 
int_from_binary_test() ->
    ?assertEqual(42, hb_util:int(<<"42">>)).
 
int_from_string_test() ->
    ?assertEqual(42, hb_util:int("42")).
 
int_passthrough_test() ->
    ?assertEqual(42, hb_util:int(42)).
 
int_negative_test() ->
    ?assertEqual(-123, hb_util:int(<<"-123">>)).
 
float_from_binary_test() ->
    ?assertEqual(3.14, hb_util:float(<<"3.14">>)).
 
float_from_integer_test() ->
    ?assertEqual(42.0, hb_util:float(42)).
 
float_passthrough_test() ->
    ?assertEqual(3.14, hb_util:float(3.14)).
 
bin_from_atom_test() ->
    ?assertEqual(<<"test">>, hb_util:bin(test)).
 
bin_from_integer_test() ->
    ?assertEqual(<<"42">>, hb_util:bin(42)).
 
bin_from_string_test() ->
    ?assertEqual(<<"hello">>, hb_util:bin("hello")).
 
bin_passthrough_test() ->
    ?assertEqual(<<"data">>, hb_util:bin(<<"data">>)).
 
bin_from_float_test() ->
    Bin = hb_util:bin(3.14),
    ?assert(is_binary(Bin)).
 
list_from_binary_test() ->
    ?assertEqual("hello", hb_util:list(<<"hello">>)).
 
list_from_atom_test() ->
    ?assertEqual("test", hb_util:list(test)).
 
list_passthrough_test() ->
    ?assertEqual("hello", hb_util:list("hello")).
 
map_from_list_test() ->
    ?assertEqual(#{a => 1, b => 2}, hb_util:map([{a, 1}, {b, 2}])).
 
map_passthrough_test() ->
    ?assertEqual(#{a => 1}, hb_util:map(#{a => 1})).

2. Encoding: encode/1, decode/1, safe_encode/1, safe_decode/1

-spec encode(binary()) -> binary().
-spec decode(binary()) -> binary().
-spec safe_encode(term()) -> binary().
-spec safe_decode(binary()) -> {ok, binary()} | {error, invalid}.

Description: URL-safe base64 encoding/decoding for IDs and binary data.

Test Code:
-module(hb_util_encode_test).
-include_lib("eunit/include/eunit.hrl").
 
encode_decode_roundtrip_test() ->
    Original = crypto:strong_rand_bytes(32),
    Encoded = hb_util:encode(Original),
    Decoded = hb_util:decode(Encoded),
    ?assertEqual(Original, Decoded).
 
encode_produces_43_bytes_test() ->
    Data = crypto:strong_rand_bytes(32),
    Encoded = hb_util:encode(Data),
    ?assertEqual(43, byte_size(Encoded)).
 
encode_url_safe_test() ->
    Data = crypto:strong_rand_bytes(100),
    Encoded = hb_util:encode(Data),
    ?assertEqual(nomatch, binary:match(Encoded, <<"+">>)),
    ?assertEqual(nomatch, binary:match(Encoded, <<"/">>)).
 
safe_decode_valid_test() ->
    Valid = hb_util:encode(<<"test">>),
    ?assertMatch({ok, <<"test">>}, hb_util:safe_decode(Valid)).
 
safe_decode_invalid_test() ->
    ?assertEqual({error, invalid}, hb_util:safe_decode(<<"invalid!@#$%">>)).
 
safe_encode_binary_test() ->
    ?assertEqual(hb_util:encode(<<"test">>), hb_util:safe_encode(<<"test">>)).
 
safe_encode_non_binary_test() ->
    ?assertEqual(not_binary, hb_util:safe_encode(not_binary)).

3. ID Operations: id/1, id/2, native_id/1, human_id/1, to_hex/1

-spec id(Item) -> binary().
-spec native_id(binary()) -> binary().
-spec human_id(binary()) -> binary().
-spec to_hex(binary()) -> binary().

Description: ID conversion between native (32-byte) and human-readable (43-byte base64) formats.

Test Code:
-module(hb_util_id_test).
-include_lib("eunit/include/eunit.hrl").
 
native_to_human_test() ->
    NativeID = crypto:strong_rand_bytes(32),
    HumanID = hb_util:human_id(NativeID),
    ?assertEqual(43, byte_size(HumanID)).
 
human_to_native_test() ->
    NativeID = crypto:strong_rand_bytes(32),
    HumanID = hb_util:human_id(NativeID),
    ?assertEqual(NativeID, hb_util:native_id(HumanID)).
 
human_id_passthrough_test() ->
    HumanID = hb_util:encode(crypto:strong_rand_bytes(32)),
    ?assertEqual(HumanID, hb_util:human_id(HumanID)).
 
native_id_passthrough_test() ->
    NativeID = crypto:strong_rand_bytes(32),
    ?assertEqual(NativeID, hb_util:native_id(NativeID)).
 
to_hex_test() ->
    Hex = hb_util:to_hex(<<255, 0, 127>>),
    ?assertEqual(<<"ff007f">>, Hex).
 
to_hex_lowercase_test() ->
    Hex = hb_util:to_hex(<<171, 205>>),
    ?assertEqual(<<"abcd">>, Hex).

4. Deep Data: deep_merge/3, deep_set/4, deep_get/3-4

-spec deep_merge(map(), map(), Opts) -> map().
-spec deep_set(Path, Value, Map, Opts) -> map().
-spec deep_get(Path, Map, Opts) -> term().
-spec deep_get(Path, Map, Default, Opts) -> term().

Description: Deep operations on nested map structures with path-based access.

Test Code:
-module(hb_util_deep_test).
-include_lib("eunit/include/eunit.hrl").
 
deep_set_single_key_test() ->
    Map = #{},
    Updated = hb_util:deep_set([<<"a">>], 1, Map, #{}),
    ?assertEqual(#{<<"a">> => 1}, Updated).
 
deep_set_nested_test() ->
    Map = #{},
    Updated = hb_util:deep_set([<<"a">>, <<"b">>, <<"c">>], 123, Map, #{}),
    ?assertEqual(#{<<"a">> => #{<<"b">> => #{<<"c">> => 123}}}, Updated).
 
deep_get_existing_test() ->
    Map = #{<<"a">> => #{<<"b">> => 42}},
    ?assertEqual(42, hb_util:deep_get([<<"a">>, <<"b">>], Map, #{})).
 
deep_get_missing_test() ->
    Map = #{<<"a">> => 1},
    ?assertEqual(not_found, hb_util:deep_get([<<"x">>, <<"y">>], Map, #{})).
 
deep_get_with_default_test() ->
    Map = #{},
    ?assertEqual(default_val, hb_util:deep_get([<<"missing">>], Map, default_val, #{})).
 
deep_merge_nested_test() ->
    Map1 = #{<<"a">> => #{<<"b">> => 1}},
    Map2 = #{<<"a">> => #{<<"c">> => 2}},
    Merged = hb_util:deep_merge(Map1, Map2, #{}),
    ?assertEqual(#{<<"a">> => #{<<"b">> => 1, <<"c">> => 2}}, Merged).
 
deep_merge_overwrite_test() ->
    Map1 = #{<<"a">> => 1},
    Map2 = #{<<"a">> => 2},
    Merged = hb_util:deep_merge(Map1, Map2, #{}),
    ?assertEqual(#{<<"a">> => 2}, Merged).
 
deep_set_unset_test() ->
    Map = #{<<"a">> => 1, <<"b">> => 2},
    Updated = hb_util:deep_set([<<"a">>], unset, Map, #{}),
    ?assertEqual(#{<<"b">> => 2}, Updated).

5. List Operations: unique/1, list_with/2, list_without/2, list_replace/3

-spec unique(list()) -> list().
-spec list_with(list(), list()) -> list().
-spec list_without(list(), list()) -> list().
-spec list_replace(list(), term(), term()) -> list().

Description: List manipulation utilities maintaining order and uniqueness.

Test Code:
-module(hb_util_list_test).
-include_lib("eunit/include/eunit.hrl").
 
unique_removes_duplicates_test() ->
    ?assertEqual([1, 2, 3], hb_util:unique([1, 2, 2, 3, 1, 3])).
 
unique_preserves_order_test() ->
    ?assertEqual([3, 1, 2], hb_util:unique([3, 1, 2, 1, 3])).
 
unique_empty_test() ->
    ?assertEqual([], hb_util:unique([])).
 
list_with_intersection_test() ->
    ?assertEqual([1, 3], hb_util:list_with([1, 2, 3], [1, 3, 4])).
 
list_with_empty_test() ->
    ?assertEqual([], hb_util:list_with([1, 2], [3, 4])).
 
list_with_all_match_test() ->
    ?assertEqual([1, 2], hb_util:list_with([1, 2], [1, 2, 3])).
 
list_without_removes_test() ->
    ?assertEqual([2], hb_util:list_without([1, 3], [1, 2, 3])).
 
list_without_none_match_test() ->
    ?assertEqual([1, 2, 3], hb_util:list_without([4, 5], [1, 2, 3])).
 
list_without_all_match_test() ->
    ?assertEqual([], hb_util:list_without([1, 2, 3], [1, 2, 3])).
 
list_replace_single_test() ->
    ?assertEqual([1, 99, 3], hb_util:list_replace([1, 2, 3], 2, 99)).
 
list_replace_with_list_test() ->
    ?assertEqual([1, a, b, 3], hb_util:list_replace([1, 2, 3], 2, [a, b])).
 
list_replace_not_found_test() ->
    ?assertEqual([1, 2, 3], hb_util:list_replace([1, 2, 3], 99, 0)).

6. Sorting: to_sorted_list/1-2, to_sorted_keys/1-2

-spec to_sorted_list(map() | list()) -> list().
-spec to_sorted_keys(map() | list()) -> list().

Description: Deterministic sorting of maps and key-value lists.

Test Code:
-module(hb_util_sort_test).
-include_lib("eunit/include/eunit.hrl").
 
to_sorted_list_map_test() ->
    Map = #{c => 3, a => 1, b => 2},
    ?assertEqual([{a, 1}, {b, 2}, {c, 3}], hb_util:to_sorted_list(Map)).
 
to_sorted_list_kv_test() ->
    KV = [{z, 1}, {a, 2}, {m, 3}],
    ?assertEqual([{a, 2}, {m, 3}, {z, 1}], hb_util:to_sorted_list(KV)).
 
to_sorted_list_plain_list_test() ->
    ?assertEqual([1, 2, 3], hb_util:to_sorted_list([3, 1, 2])).
 
to_sorted_keys_map_test() ->
    Map = #{z => 1, a => 2, m => 3},
    ?assertEqual([a, m, z], hb_util:to_sorted_keys(Map)).
 
to_sorted_keys_list_test() ->
    ?assertEqual([a, b, c], hb_util:to_sorted_keys([c, a, b])).

7. Statistics: count/2, mean/1, stddev/1, variance/1, weighted_random/1

-spec count(term(), list()) -> integer().
-spec mean(list()) -> float().
-spec stddev(list()) -> float().
-spec variance(list()) -> float().
-spec weighted_random([{term(), number()}]) -> term().

Description: Statistical functions and weighted random selection.

Test Code:
-module(hb_util_stats_test).
-include_lib("eunit/include/eunit.hrl").
 
count_test() ->
    ?assertEqual(3, hb_util:count(a, [a, b, a, c, a])).
 
count_zero_test() ->
    ?assertEqual(0, hb_util:count(x, [a, b, c])).
 
mean_test() ->
    ?assertEqual(3.0, hb_util:mean([1, 2, 3, 4, 5])).
 
mean_single_test() ->
    ?assertEqual(42.0, hb_util:mean([42])).
 
variance_test() ->
    Var = hb_util:variance([1, 2, 3, 4, 5]),
    ?assertEqual(2.0, Var).
 
stddev_test() ->
    StdDev = hb_util:stddev([1, 2, 3, 4, 5]),
    ?assert(abs(StdDev - 1.4142135623730951) < 0.0001).
 
weighted_random_returns_valid_item_test() ->
    Items = [{a, 10}, {b, 5}, {c, 1}],
    Result = hb_util:weighted_random(Items),
    ?assert(lists:member(Result, [a, b, c])).
 
weighted_random_respects_weights_test() ->
    % With extreme weights, heavily weighted item should appear most
    Items = [{heavy, 1000}, {light, 1}],
    Results = [hb_util:weighted_random(Items) || _ <- lists:seq(1, 100)],
    HeavyCount = length([X || X <- Results, X == heavy]),
    ?assert(HeavyCount > 80).

8. Rounding: ceil_int/2, floor_int/2

-spec ceil_int(integer(), integer()) -> integer().
-spec floor_int(integer(), integer()) -> integer().

Description: Round integers to nearest multiple.

Test Code:
-module(hb_util_round_test).
-include_lib("eunit/include/eunit.hrl").
 
ceil_int_rounds_up_test() ->
    ?assertEqual(10, hb_util:ceil_int(7, 5)).
 
ceil_int_exact_multiple_test() ->
    ?assertEqual(10, hb_util:ceil_int(5, 5)).
 
ceil_int_large_test() ->
    ?assertEqual(1000, hb_util:ceil_int(999, 100)).
 
floor_int_rounds_down_test() ->
    ?assertEqual(5, hb_util:floor_int(7, 5)).
 
floor_int_exact_multiple_test() ->
    ?assertEqual(5, hb_util:floor_int(5, 5)).
 
floor_int_large_test() ->
    ?assertEqual(900, hb_util:floor_int(999, 100)).

9. Condition Waiting: until/1-3

-spec until(fun(() -> boolean())) -> integer().
-spec until(fun(() -> boolean()), integer()) -> integer().
-spec until(fun(() -> boolean()), fun(), integer()) -> integer().

Description: Wait for condition to be true, optionally executing function between checks.

Test Code:
-module(hb_util_until_test).
-include_lib("eunit/include/eunit.hrl").
 
until_immediate_true_test() ->
    Count = hb_util:until(fun() -> true end),
    ?assertEqual(0, Count).
 
until_with_iterations_test() ->
    put(until_counter, 0),
    Count = hb_util:until(
        fun() -> get(until_counter) >= 3 end,
        fun() ->
            put(until_counter, get(until_counter) + 1),
            1
        end,
        0
    ),
    ?assertEqual(3, Count).
 
until_returns_iteration_count_test() ->
    put(until_counter2, 0),
    Count = hb_util:until(
        fun() -> get(until_counter2) >= 5 end,
        fun() ->
            put(until_counter2, get(until_counter2) + 1),
            1
        end,
        0
    ),
    ?assertEqual(5, Count).

10. Formatting: human_int/1, to_lower/1

-spec human_int(integer() | float()) -> string().
-spec to_lower(binary() | string()) -> binary() | string().

Description: Human-readable formatting utilities.

Test Code:
-module(hb_util_format_test).
-include_lib("eunit/include/eunit.hrl").
 
human_int_thousands_test() ->
    ?assertEqual("1,234", hb_util:human_int(1234)).
 
human_int_millions_test() ->
    ?assertEqual("1,234,567", hb_util:human_int(1234567)).
 
human_int_small_test() ->
    ?assertEqual("100", hb_util:human_int(100)).
 
human_int_zero_test() ->
    ?assertEqual("0", hb_util:human_int(0)).
 
human_int_float_test() ->
    ?assertEqual("1,235", hb_util:human_int(1234.6)).
 
to_lower_binary_test() ->
    ?assertEqual(<<"hello">>, hb_util:to_lower(<<"HELLO">>)).
 
to_lower_mixed_test() ->
    ?assertEqual(<<"hello world">>, hb_util:to_lower(<<"Hello World">>)).

11. Value Finding: find_value/2-4

-spec find_value(term(), map() | list()) -> term().
-spec find_value(term(), map() | list(), term()) -> term().

Description: Find values in maps or key-value lists.

Test Code:
-module(hb_util_find_test).
-include_lib("eunit/include/eunit.hrl").
 
find_value_in_map_test() ->
    Map = #{a => 1, b => 2},
    ?assertEqual(1, hb_util:find_value(a, Map)).
 
find_value_in_list_test() ->
    List = [{a, 1}, {b, 2}],
    ?assertEqual(1, hb_util:find_value(a, List)).
 
find_value_missing_test() ->
    ?assertEqual(undefined, hb_util:find_value(x, #{a => 1})).
 
find_value_with_default_test() ->
    ?assertEqual(default, hb_util:find_value(x, #{a => 1}, default)).

12. String Operations: remove_common/2, key_to_atom/1-2

-spec remove_common(binary() | string(), binary() | string()) -> binary() | string().
-spec key_to_atom(binary() | atom()) -> atom().

Description: String manipulation and key normalization.

Test Code:
-module(hb_util_string_test).
-include_lib("eunit/include/eunit.hrl").
 
remove_common_prefix_test() ->
    ?assertEqual(<<"world">>, hb_util:remove_common(<<"hello world">>, <<"hello ">>)).
 
remove_common_no_match_test() ->
    ?assertEqual(<<"abc">>, hb_util:remove_common(<<"abc">>, <<"xyz">>)).
 
remove_common_full_match_test() ->
    ?assertEqual(<<>>, hb_util:remove_common(<<"test">>, <<"test">>)).
 
key_to_atom_passthrough_test() ->
    ?assertEqual(test, hb_util:key_to_atom(test)).
 
key_to_atom_converts_dashes_test() ->
    % Note: requires the atom to already exist
    ?assertEqual(hello_world, hb_util:key_to_atom(<<"hello-world">>)).

13. Validation: check_size/2, check_type/2, check_value/2

-spec check_size(binary(), list() | {range, integer(), integer()}) -> boolean().
-spec check_type(term(), atom()) -> boolean().
-spec check_value(term(), list()) -> boolean().

Description: Validation utilities for type and size checking.

Test Code:
-module(hb_util_check_test).
-include_lib("eunit/include/eunit.hrl").
 
check_size_valid_test() ->
    ?assert(hb_util:check_size(<<"abc">>, [3])).
 
check_size_invalid_test() ->
    ?assertNot(hb_util:check_size(<<"abc">>, [5])).
 
check_size_range_test() ->
    ?assert(hb_util:check_size(<<"abc">>, {range, 1, 5})).
 
check_size_range_invalid_test() ->
    ?assertNot(hb_util:check_size(<<"abc">>, {range, 5, 10})).
 
check_type_binary_test() ->
    ?assert(hb_util:check_type(<<"test">>, binary)).
 
check_type_integer_test() ->
    ?assert(hb_util:check_type(42, integer)).
 
check_type_list_test() ->
    ?assert(hb_util:check_type([1, 2], list)).
 
check_type_map_test() ->
    ?assert(hb_util:check_type(#{}, map)).
 
check_type_wrong_test() ->
    ?assertNot(hb_util:check_type(<<"binary">>, integer)).
 
check_value_valid_test() ->
    ?assert(hb_util:check_value(a, [a, b, c])).
 
check_value_invalid_test() ->
    ?assertNot(hb_util:check_value(x, [a, b, c])).

14. Error Handling: ok/1-2, maybe_throw/2, ok_or_throw/3

-spec ok(term()) -> term().
-spec ok(term(), map()) -> term().
-spec maybe_throw(term(), map()) -> term() | no_return().
-spec ok_or_throw(term(), boolean(), term()) -> true | no_return().

Description: Error handling and unwrapping utilities.

Test Code:
-module(hb_util_error_test).
-include_lib("eunit/include/eunit.hrl").
 
ok_unwrap_test() ->
    ?assertEqual(value, hb_util:ok({ok, value})).
 
ok_throw_on_error_test() ->
    ?assertThrow({unexpected, error}, hb_util:ok(error)).
 
ok_or_throw_true_test() ->
    ?assertEqual(true, hb_util:ok_or_throw(ignored, true, some_error)).
 
ok_or_throw_false_test() ->
    ?assertThrow(some_error, hb_util:ok_or_throw(ignored, false, some_error)).
 
maybe_throw_with_throw_strategy_test() ->
    ?assertThrow(my_error, hb_util:maybe_throw(my_error, #{error_strategy => throw})).

15. Atom Conversion: atom/1

-spec atom(binary() | list() | atom()) -> atom().

Description: Convert string/binary to existing atom. Throws if atom doesn't exist.

Test Code:
-module(hb_util_atom_test).
-include_lib("eunit/include/eunit.hrl").
 
atom_passthrough_test() ->
    ?assertEqual(test, hb_util:atom(test)).
 
atom_from_binary_test() ->
    ?assertEqual(true, hb_util:atom(<<"true">>)).
 
atom_from_string_test() ->
    ?assertEqual(false, hb_util:atom("false")).

16. String Predicates: is_string_list/1, unquote/1

-spec is_string_list(term()) -> boolean().
-spec unquote(binary()) -> binary().

Description: String validation and quote removal.

Test Code:
-module(hb_util_string_pred_test).
-include_lib("eunit/include/eunit.hrl").
 
is_string_list_true_test() ->
    ?assert(hb_util:is_string_list("hello")).
 
is_string_list_false_test() ->
    ?assertNot(hb_util:is_string_list([a, b, c])).
 
is_string_list_empty_test() ->
    ?assert(hb_util:is_string_list([])).
 
unquote_quoted_test() ->
    ?assertEqual(<<"hello">>, hb_util:unquote(<<"\"hello\"">>)).
 
unquote_unquoted_test() ->
    ?assertEqual(<<"hello">>, hb_util:unquote(<<"hello">>)).
 
unquote_partial_test() ->
    ?assertEqual(<<"hello">>, hb_util:unquote(<<"\"hello">>)).

17. String Parsing: split_depth_string_aware/2, split_escaped_single/2

-spec split_depth_string_aware(char() | [char()], binary()) -> [binary()].
-spec split_escaped_single(char(), binary()) -> {binary(), binary()}.

Description: Advanced string splitting with nesting and escape awareness. Separator is a character (integer) or list of characters, not a binary.

Test Code:
-module(hb_util_split_test).
-include_lib("eunit/include/eunit.hrl").
 
split_simple_test() ->
    %% Separator is $, (comma character = 44), not <<",">>
    ?assertEqual([<<"a">>, <<"b">>, <<"c">>], 
                 hb_util:split_depth_string_aware($,, <<"a,b,c">>)).
 
split_with_quotes_test() ->
    %% Quoted strings are preserved (comma inside quotes not split)
    ?assertEqual([<<"\"a,b\"">>, <<"c">>],
                 hb_util:split_depth_string_aware($,, <<"\"a,b\",c">>)).
 
split_with_parens_test() ->
    %% Parentheses create depth (comma inside parens not split)
    ?assertEqual([<<"(a,b)">>, <<"c">>],
                 hb_util:split_depth_string_aware($,, <<"(a,b),c">>)).
 
split_empty_test() ->
    ?assertEqual([], hb_util:split_depth_string_aware($,, <<>>)).
 
split_escaped_test() ->
    {Part, Rest} = hb_util:split_escaped_single($,, <<"a\\,b,c">>),
    ?assertEqual(<<"a\\,b">>, Part),
    ?assertEqual(<<"c">>, Rest).

18. Binary/String Utilities: binary_to_strings/1, binary_is_atom/1

-spec binary_to_strings(binary() | list()) -> list().
-spec binary_is_atom(binary()) -> boolean().

Description: Binary parsing and atom checking utilities.

Test Code:
-module(hb_util_binary_test).
-include_lib("eunit/include/eunit.hrl").
 
binary_to_strings_passthrough_test() ->
    ?assertEqual([a, b], hb_util:binary_to_strings([a, b])).
 
binary_is_atom_true_test() ->
    ?assert(hb_util:binary_is_atom(<<"true">>)).
 
binary_is_atom_false_test() ->
    ?assertNot(hb_util:binary_is_atom(<<"not_an_existing_atom_xyz123">>)).

19. Numbered Message Operations: number/1, hd/1-3

-spec number(list()) -> [{binary(), term()}].
-spec hd(map()) -> term().
-spec hd(map(), key | value) -> term().
-spec hd(map(), key | value, map()) -> term().

Description: Number list elements and get first element from numbered maps. hd/1-3 gets the first element (lowest integer key >= 1) from a numbered map.

Test Code:
-module(hb_util_numbered_test).
-include_lib("eunit/include/eunit.hrl").
 
number_test() ->
    ?assertEqual([{<<"1">>, a}, {<<"2">>, b}], hb_util:number([a, b])).
 
number_empty_test() ->
    ?assertEqual([], hb_util:number([])).
 
hd_value_test() ->
    %% Get first value from numbered map - returns {ok, Value}
    Map = #{<<"1">> => first, <<"2">> => second},
    ?assertEqual({ok, first}, hb_util:hd(Map)).
 
hd_key_test() ->
    %% Get first key from numbered map
    Map = #{<<"1">> => first, <<"2">> => second},
    ?assertEqual(<<"1">>, hb_util:hd(Map, key)).
 
hd_empty_throws_test() ->
    %% Empty map throws by default
    ?assertThrow(no_integer_keys, hb_util:hd(#{})).
 
hd_empty_with_return_strategy_test() ->
    %% With error_strategy => return, returns undefined
    ?assertEqual(undefined, hb_util:hd(#{}, value, #{error_strategy => return})).

20. Ordered List Operations: is_ordered_list/2, list_to_numbered_message/1

-spec is_ordered_list(map(), map()) -> boolean().
-spec list_to_numbered_message(list() | map()) -> map().

Description: Check and convert ordered numbered message structures.

Test Code:
-module(hb_util_ordered_test).
-include_lib("eunit/include/eunit.hrl").
 
is_ordered_list_true_test() ->
    ?assert(hb_util:is_ordered_list([a, b, c], #{})).
 
list_to_numbered_message_test() ->
    Result = hb_util:list_to_numbered_message([a, b, c]),
    ?assert(is_map(Result)).

21. Map Case Operations: lower_case_key_map/2

-spec lower_case_key_map(map(), map()) -> map().

Description: Recursively lowercase all keys in a map.

Test Code:
-module(hb_util_case_test).
-include_lib("eunit/include/eunit.hrl").
 
lower_case_key_map_test() ->
    Map = #{<<"Hello">> => 1, <<"WORLD">> => 2},
    Result = hb_util:lower_case_key_map(Map, #{}),
    ?assertEqual(#{<<"hello">> => 1, <<"world">> => 2}, Result).
 
lower_case_key_map_nested_test() ->
    Map = #{<<"Outer">> => #{<<"Inner">> => 1}},
    Result = hb_util:lower_case_key_map(Map, #{}),
    ?assertEqual(#{<<"outer">> => #{<<"inner">> => 1}}, Result).

22. Module Introspection: is_hb_module/1-2, all_hb_modules/0, all_atoms/0

-spec is_hb_module(atom()) -> boolean().
-spec is_hb_module(atom(), [string()]) -> boolean().
-spec all_hb_modules() -> [atom()].
-spec all_atoms() -> [atom()].

Description: Check if module is part of HyperBEAM based on prefix matching. is_hb_module/2 takes explicit prefixes list. Note: hb_event is explicitly excluded (always returns false).

Test Code:
-module(hb_util_module_test).
-include_lib("eunit/include/eunit.hrl").
 
is_hb_module_with_prefix_test() ->
    %% With explicit prefixes, checks if module starts with prefix
    ?assert(hb_util:is_hb_module(hb_util, ["hb"])),
    ?assert(hb_util:is_hb_module(hb_cache, ["hb"])),
    ?assert(hb_util:is_hb_module(ar_wallet, ["ar"])).
 
is_hb_module_no_match_test() ->
    %% Module not matching any prefix returns false
    ?assertNot(hb_util:is_hb_module(lists, ["hb", "ar"])),
    ?assertNot(hb_util:is_hb_module(crypto, ["hb"])).
 
is_hb_module_hb_event_excluded_test() ->
    %% hb_event is explicitly excluded (always false)
    ?assertNot(hb_util:is_hb_module(hb_event, ["hb"])).
 
is_hb_module_empty_prefixes_test() ->
    %% Empty prefixes list means nothing matches
    ?assertNot(hb_util:is_hb_module(hb_util, [])).
 
all_hb_modules_returns_list_test() ->
    %% Returns a list of atoms
    Modules = hb_util:all_hb_modules(),
    ?assert(is_list(Modules)),
    lists:foreach(fun(M) -> ?assert(is_atom(M)) end, Modules).
 
all_atoms_returns_list_test() ->
    Atoms = hb_util:all_atoms(),
    ?assert(is_list(Atoms)),
    ?assert(length(Atoms) > 0).
 
all_atoms_contains_common_atoms_test() ->
    Atoms = hb_util:all_atoms(),
    ?assert(lists:member(true, Atoms)),
    ?assert(lists:member(false, Atoms)),
    ?assert(lists:member(ok, Atoms)),
    ?assert(lists:member(error, Atoms)).

Common Patterns

%% Type conversion from HTTP input
handle_param(BinValue) ->
    IntValue = hb_util:int(BinValue),
    process(IntValue).
 
%% ID handling
get_message_id(Msg) ->
    NativeID = hb_message:id(Msg),
    HumanID = hb_util:human_id(NativeID).
 
%% Deep data manipulation
update_nested(State, Path, Value) ->
    hb_util:deep_set(Path, Value, State, #{}).
 
%% List operations
get_unique_items(Lists) ->
    AllItems = lists:flatten(Lists),
    hb_util:unique(AllItems).
 
%% Statistics
analyze_performance(Samples) ->
    #{
        mean => hb_util:mean(Samples),
        stddev => hb_util:stddev(Samples)
    }.
 
%% Wait for condition
wait_for_ready(System) ->
    hb_util:until(
        fun() -> is_ready(System) end,
        fun() -> timer:sleep(100), 1 end,
        0
    ).

References

  • b64fast - Fast base64 encoding library
  • hb_ao - AO message utilities
  • hb_maps - Map operations
  • hb_path - Path handling

Notes

  1. Type Safety: Coercion functions assume valid input
  2. ID Formats: 32-byte native, 43-byte base64 human
  3. Deep Operations: Support nested map access
  4. List Order: Most functions preserve order
  5. Statistics: Standard mathematical definitions
  6. Base64: URL-safe variant used throughout
  7. Atom Safety: atom/1 requires existing atoms
  8. Error Strategy: Many functions support error_strategy option
  9. Performance: Most operations O(N) or better
  10. Pure Functions: All functions are stateless