-module(distribute@cluster). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/distribute/cluster.gleam"). -export([start_node/2, connect/1, connect_bool/1, nodes/0, self_node/0, ping/1]). -export_type([start_error/0, connect_error/0, dynamic_/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -type start_error() :: {invalid_node_name, binary()} | already_started | cookie_too_long | {network_error, binary()} | {system_error, binary()} | {start_failed, binary()}. -type connect_error() :: connect_timeout | node_not_found | {connect_network_error, binary()} | connect_ignored. -type dynamic_() :: any(). -file("src/distribute/cluster.gleam", 86). ?DOC(" Convert StartError to string for logging\n"). -spec classify_start_error_to_string(start_error()) -> binary(). classify_start_error_to_string(Error) -> case Error of {invalid_node_name, Msg} -> <<"invalid_node_name: "/utf8, Msg/binary>>; already_started -> <<"already_started"/utf8>>; cookie_too_long -> <<"cookie_too_long"/utf8>>; {network_error, Msg@1} -> <<"network_error: "/utf8, Msg@1/binary>>; {system_error, Msg@2} -> <<"system_error: "/utf8, Msg@2/binary>>; {start_failed, Msg@3} -> <<"start_failed: "/utf8, Msg@3/binary>> end. -file("src/distribute/cluster.gleam", 98). ?DOC(" Check if error is network-related\n"). -spec is_network_error(binary()) -> boolean(). is_network_error(Reason) -> (gleam_stdlib:contains_string(Reason, <<"network"/utf8>>) orelse gleam_stdlib:contains_string( Reason, <<"eaddrinuse"/utf8>> )) orelse gleam_stdlib:contains_string(Reason, <<"connection"/utf8>>). -file("src/distribute/cluster.gleam", 105). ?DOC(" Check if error is system/permission related\n"). -spec is_system_error(binary()) -> boolean(). is_system_error(Reason) -> (gleam_stdlib:contains_string(Reason, <<"permission"/utf8>>) orelse gleam_stdlib:contains_string( Reason, <<"access"/utf8>> )) orelse gleam_stdlib:contains_string(Reason, <<"enoent"/utf8>>). -file("src/distribute/cluster.gleam", 68). ?DOC(" Classify error reason into structured StartError\n"). -spec classify_start_error(binary()) -> start_error(). classify_start_error(Reason) -> case Reason of <<"already_started"/utf8>> -> already_started; <<"invalid_node_name"/utf8>> -> {invalid_node_name, <<"Node name must contain '@'"/utf8>>}; <<"cookie_too_long"/utf8>> -> cookie_too_long; _ -> case is_network_error(Reason) of true -> {network_error, Reason}; false -> case is_system_error(Reason) of true -> {system_error, Reason}; false -> {start_failed, Reason} end end end. -file("src/distribute/cluster.gleam", 155). ?DOC(" Validate node name format\n"). -spec validate_node_name(binary()) -> {ok, nil} | {error, start_error()}. validate_node_name(Name) -> case gleam_stdlib:contains_string(Name, <<"@"/utf8>>) of true -> {ok, nil}; false -> {error, {invalid_node_name, <<"Node name must contain '@' symbol"/utf8>>}} end. -file("src/distribute/cluster.gleam", 163). ?DOC(" Validate cookie length\n"). -spec validate_cookie(binary()) -> {ok, nil} | {error, start_error()}. validate_cookie(Cookie) -> case string:length(Cookie) > 255 of true -> {error, cookie_too_long}; false -> {ok, nil} end. -file("src/distribute/cluster.gleam", 113). ?DOC( " Start distributed node with the given name and cookie.\n" " Returns Ok(Nil) on success, Error with specific failure reason.\n" ). -spec start_node(binary(), binary()) -> {ok, nil} | {error, start_error()}. start_node(Name, Cookie) -> distribute@log:info( <<"Starting distributed node"/utf8>>, [{<<"node"/utf8>>, Name}] ), case validate_node_name(Name) of {error, E} -> distribute@log:error( <<"Node name validation failed"/utf8>>, [{<<"node"/utf8>>, Name}, {<<"error"/utf8>>, classify_start_error_to_string(E)}] ), {error, E}; {ok, _} -> case validate_cookie(Cookie) of {error, E@1} -> distribute@log:error( <<"Cookie validation failed"/utf8>>, [{<<"node"/utf8>>, Name}, {<<"error"/utf8>>, classify_start_error_to_string(E@1)}] ), {error, E@1}; {ok, _} -> Res = cluster_ffi:start_node(Name, Cookie), case cluster_ffi:is_ok_atom(Res) of true -> distribute@log:info( <<"Node started successfully"/utf8>>, [{<<"node"/utf8>>, Name}] ), {ok, nil}; false -> Error = classify_start_error( cluster_ffi:get_error_reason(Res) ), distribute@log:error( <<"Failed to start node"/utf8>>, [{<<"node"/utf8>>, Name}, {<<"error"/utf8>>, classify_start_error_to_string(Error)}] ), {error, Error} end end end. -file("src/distribute/cluster.gleam", 172). ?DOC( " Connect to another distributed node.\n" " Returns Ok(Nil) if connected, Error with reason otherwise.\n" ). -spec connect(binary()) -> {ok, nil} | {error, connect_error()}. connect(Node) -> Result = cluster_ffi:connect(Node), case cluster_ffi:is_true(Result) of true -> {ok, nil}; false -> case cluster_ffi:is_ignored(Result) of true -> {error, connect_ignored}; false -> {error, node_not_found} end end. -file("src/distribute/cluster.gleam", 187). ?DOC( " Connect to another distributed node (legacy API).\n" " Returns True if connected successfully, False otherwise.\n" " @deprecated Use connect() which returns Result instead\n" ). -spec connect_bool(binary()) -> boolean(). connect_bool(Node) -> cluster_ffi:is_true(cluster_ffi:connect(Node)). -file("src/distribute/cluster.gleam", 192). ?DOC(" Get the list of currently connected nodes.\n"). -spec nodes() -> list(binary()). nodes() -> cluster_ffi:nodes(). -file("src/distribute/cluster.gleam", 197). ?DOC(" Get the name of the current node.\n"). -spec self_node() -> binary(). self_node() -> cluster_ffi:self_node(). -file("src/distribute/cluster.gleam", 203). ?DOC( " Ping a remote node to check if it is reachable.\n" " Returns True if the node responds with pong, False otherwise.\n" ). -spec ping(binary()) -> boolean(). ping(Node) -> cluster_ffi:ping(Node).