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
94 lines
2.4 KiB
Elixir
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
|