towerops/lib/towerops/numeric.ex
Graham McIntire efaf5558ff refactor: convert 6 Gleam modules to idiomatic Elixir with TDD (#196)
Phase 1: Foundation Types (100% complete)
- query_helpers: SQL LIKE sanitization with pipe operators
- numeric: Integer parsing with pattern matching guards
- result: Pure Elixir Result monad (map, and_then, unwrap_or)

Phase 2: Ecto Domain Types (100% complete)
- ip_address: IPv4/IPv6 validation using :inet directly
- mac_address: Multi-format MAC parsing (colon/hyphen/dot/compact)
- snmp_oid: OID parsing/manipulation with recursive pattern matching

All 198 tests passing across converted modules.
API changed from Gleam-style {:some/:none to idiomatic {:ok/:error.
Refactored parse_numeric_oid to use with statement, reducing nesting depth.

Reviewed-on: graham/towerops-web#196
2026-03-28 09:52:07 -05:00

94 lines
2.4 KiB
Elixir

defmodule Towerops.Numeric do
@moduledoc """
Parse arbitrary BEAM terms into integers or floats.
Provides robust parsing that handles integers, floats, strings, and other types,
returning `{:ok, value}` or `:error`.
"""
@doc """
Parse an arbitrary BEAM term into an integer, if possible.
Returns `{:ok, integer}` for integers, integer strings, or `:error` for anything else.
## Examples
iex> Towerops.Numeric.parse_integer(42)
{:ok, 42}
iex> Towerops.Numeric.parse_integer("42")
{:ok, 42}
iex> Towerops.Numeric.parse_integer("42abc")
{:ok, 42}
iex> Towerops.Numeric.parse_integer("abc")
:error
iex> Towerops.Numeric.parse_integer(nil)
:error
"""
@spec parse_integer(term()) :: {:ok, integer()} | :error
def parse_integer(value) when is_integer(value), do: {:ok, value}
def parse_integer(value) when is_binary(value), do: parse_integer_string(value)
def parse_integer(_), do: :error
@doc """
Parse an arbitrary BEAM term into a float, if possible.
Returns `{:ok, float}` for floats, integers (converted), numeric strings, or `:error`.
## Examples
iex> Towerops.Numeric.parse_float(4.2)
{:ok, 4.2}
iex> Towerops.Numeric.parse_float(42)
{:ok, 42.0}
iex> Towerops.Numeric.parse_float("4.2")
{:ok, 4.2}
iex> Towerops.Numeric.parse_float("42")
{:ok, 42.0}
iex> Towerops.Numeric.parse_float("abc")
:error
"""
@spec parse_float(term()) :: {:ok, float()} | :error
def parse_float(value) when is_float(value), do: {:ok, value}
def parse_float(value) when is_integer(value), do: {:ok, value * 1.0}
def parse_float(value) when is_binary(value), do: parse_float_string(value)
def parse_float(_), do: :error
# Private helpers
defp parse_integer_string(""), do: :error
defp parse_integer_string("null"), do: :error
defp parse_integer_string(str) do
case Integer.parse(str) do
{int, _remainder} -> {:ok, int}
:error -> :error
end
end
defp parse_float_string(""), do: :error
defp parse_float_string("null"), do: :error
defp parse_float_string(str) do
case Float.parse(str) do
{float, _remainder} ->
{:ok, float}
:error ->
# Try parsing as integer string, then convert to float
case Integer.parse(str) do
{int, _remainder} -> {:ok, int * 1.0}
:error -> :error
end
end
end
end