defmodule SnmpKit.SnmpLib.Error do @moduledoc """ Standard SNMP error handling and error code utilities. Core logic is implemented in Gleam (`:snmpkit@snmp_lib@error`). This wrapper provides the idiomatic Elixir API that accepts atoms, integers, and strings as error status identifiers. """ @gleam :snmpkit@snmp_lib@error @ffi :snmpkit_error_ffi @type error_status :: :no_error | :too_big | :no_such_name | :bad_value | :read_only | :gen_err | non_neg_integer() @type error_index :: non_neg_integer() @type varbind :: {list(), any()} @type varbinds :: [varbind()] ## Standard Error Code Constants @spec no_error() :: 0 def no_error, do: 0 @spec too_big() :: 1 def too_big, do: 1 @spec no_such_name() :: 2 def no_such_name, do: 2 @spec bad_value() :: 3 def bad_value, do: 3 @spec read_only() :: 4 def read_only, do: 4 @spec gen_err() :: 5 def gen_err, do: 5 ## Error Utilities @spec error_name(error_status()) :: String.t() def error_name(code) when is_integer(code) do code |> @gleam.from_code() |> @gleam.error_name() end def error_name(atom) when is_atom(atom) do atom |> @ffi.atom_to_error_status() |> @gleam.error_name() end @spec error_atom(error_status()) :: atom() def error_atom(code) when is_integer(code) do code |> @gleam.from_code() |> @ffi.error_status_to_atom() end def error_atom(atom) when is_atom(atom), do: atom @spec error_code(atom() | String.t()) :: non_neg_integer() def error_code(atom) when is_atom(atom) do status = @ffi.atom_to_error_status(atom) code = @gleam.error_code(status) # UnknownError returns -1 from Gleam; map to gen_err (5) for compatibility if code == -1, do: 5, else: code end def error_code(name) when is_binary(name) do status = @gleam.from_name(name) code = @gleam.error_code(status) if code == -1, do: 5, else: code end def error_code(_), do: 5 @spec format_error(error_status(), error_index(), varbinds()) :: String.t() def format_error(error_status, error_index, varbinds \\ []) do status = to_gleam_status(error_status) @gleam.format_error(status, error_index, varbinds) end @spec retriable_error?(error_status()) :: boolean() def retriable_error?(error_status) do error_status |> to_gleam_status() |> @gleam.is_retriable() end @spec create_error_response(map(), error_status(), error_index()) :: {:ok, map()} | {:error, atom()} def create_error_response(request_pdu, error_status, error_index) do case validate_request_pdu(request_pdu) do :ok -> error_code_num = if is_integer(error_status), do: error_status, else: error_code(error_status) error_response = %{ type: :get_response, request_id: Map.get(request_pdu, :request_id, 0), error_status: error_code_num, error_index: error_index, varbinds: Map.get(request_pdu, :varbinds, []) } {:ok, error_response} {:error, reason} -> {:error, reason} end end @spec valid_error_status?(any()) :: boolean() def valid_error_status?(status) when is_integer(status) do @gleam.is_valid_code(status) end def valid_error_status?(status) when is_atom(status) do error_name(status) != "unknown_error" end def valid_error_status?(_), do: false @spec all_error_codes() :: [non_neg_integer()] def all_error_codes, do: @gleam.all_codes() @spec all_error_atoms() :: [atom()] def all_error_atoms do [ :no_error, :too_big, :no_such_name, :bad_value, :read_only, :gen_err, :no_access, :wrong_type, :wrong_length, :wrong_encoding, :wrong_value, :no_creation, :inconsistent_value, :resource_unavailable, :commit_failed, :undo_failed, :authorization_error, :not_writable, :inconsistent_name ] end @spec error_severity(error_status()) :: :info | :warning | :error def error_severity(error_status) do error_status |> to_gleam_status() |> @gleam.severity() |> String.to_atom() end ## Private Helpers defp to_gleam_status(code) when is_integer(code), do: @gleam.from_code(code) defp to_gleam_status(atom) when is_atom(atom), do: @ffi.atom_to_error_status(atom) defp validate_request_pdu(request_pdu) when is_map(request_pdu), do: :ok defp validate_request_pdu(_), do: {:error, :invalid_request_pdu} end