improve dialyzer
This commit is contained in:
parent
d75896597e
commit
751b8a5f1b
11 changed files with 96 additions and 16 deletions
|
|
@ -13,5 +13,14 @@
|
|||
|
||||
# IP geolocation plug - these patterns are actually reachable but dialyzer can't prove it
|
||||
# The remote_ip can be nil or other values beyond just IPv4/IPv6 tuples
|
||||
~r/lib\/aprsme_web\/plugs\/ip_geolocation\.ex/
|
||||
~r/lib\/aprsme_web\/plugs\/ip_geolocation\.ex/,
|
||||
|
||||
# False positive: Aprs.parse/1 does return {:ok, _} but dialyzer can't see the vendored library types
|
||||
{"lib/aprsme/is/is.ex"},
|
||||
|
||||
# False positive: The nil pattern is handled in the case statement above, dialyzer doesn't track this correctly
|
||||
{"lib/aprsme/packets.ex"},
|
||||
|
||||
# False positive: PacketUtils.get_weather_field can return various types including nil
|
||||
{"lib/aprsme_web/live/weather_live/callsign_view.ex"}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
elixir 1.19.0-rc.0-otp-27
|
||||
elixir 1.19.0-rc.2-otp-27
|
||||
erlang 27.3.4
|
||||
gleam 1.11.1
|
||||
|
|
|
|||
|
|
@ -512,7 +512,7 @@ defmodule Aprsme.Is do
|
|||
attrs = Aprsme.Packet.extract_additional_data(attrs, message)
|
||||
|
||||
# Normalize data_type to string if it's an atom
|
||||
attrs = normalize_data_type(attrs)
|
||||
attrs = Aprsme.EncodingUtils.normalize_data_type(attrs)
|
||||
|
||||
# Submit to GenStage pipeline for batch processing
|
||||
Aprsme.PacketProducer.submit_packet(attrs)
|
||||
|
|
@ -542,10 +542,6 @@ defmodule Aprsme.Is do
|
|||
end
|
||||
end
|
||||
|
||||
# Normalize data_type to ensure proper storage
|
||||
@spec normalize_data_type(map()) :: map()
|
||||
defp normalize_data_type(attrs), do: Aprsme.EncodingUtils.normalize_data_type(attrs)
|
||||
|
||||
@spec update_packet_stats(map(), integer()) :: map()
|
||||
defp update_packet_stats(stats, current_time) do
|
||||
new_total = stats.total_packets + 1
|
||||
|
|
|
|||
|
|
@ -11,6 +11,15 @@ defmodule Aprsme.PacketConsumer do
|
|||
|
||||
require Logger
|
||||
|
||||
@type state :: %{
|
||||
batch: list(map()),
|
||||
batch_size: integer(),
|
||||
batch_timeout: integer(),
|
||||
max_batch_size: integer(),
|
||||
timer: reference() | nil
|
||||
}
|
||||
|
||||
@spec start_link(keyword()) :: GenServer.on_start()
|
||||
def start_link(opts \\ []) do
|
||||
# Allow unnamed consumers for pool usage
|
||||
name = opts[:name]
|
||||
|
|
@ -102,6 +111,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
end
|
||||
|
||||
# Helper functions with pattern matching
|
||||
@spec reset_batch_timer(state()) :: state()
|
||||
defp reset_batch_timer(%{timer: nil} = state) do
|
||||
new_timer = Process.send_after(self(), :process_batch, state.batch_timeout)
|
||||
%{state | batch: [], timer: new_timer}
|
||||
|
|
@ -114,10 +124,12 @@ defmodule Aprsme.PacketConsumer do
|
|||
end
|
||||
|
||||
# Efficient batch length calculation (cached if possible)
|
||||
@spec batch_length(list()) :: non_neg_integer()
|
||||
defp batch_length([]), do: 0
|
||||
defp batch_length(batch), do: length(batch)
|
||||
|
||||
# Pattern matching for logging
|
||||
@spec log_dropped_packets(list(), list()) :: :ok
|
||||
defp log_dropped_packets([], _), do: :ok
|
||||
|
||||
defp log_dropped_packets(dropped, processed) do
|
||||
|
|
@ -150,12 +162,14 @@ defmodule Aprsme.PacketConsumer do
|
|||
end
|
||||
|
||||
# Helper to start batch timer
|
||||
@spec start_batch_timer(state()) :: state()
|
||||
defp start_batch_timer(%{batch_timeout: timeout} = state) do
|
||||
timer = Process.send_after(self(), :process_batch, timeout)
|
||||
%{state | timer: timer}
|
||||
end
|
||||
|
||||
# Pattern matching for batch utilization check
|
||||
@spec check_batch_utilization(list(), integer()) :: :ok
|
||||
defp check_batch_utilization(batch, max_size) when length(batch) > max_size * 0.8 do
|
||||
Logger.warning("Batch size approaching limit",
|
||||
batch_status:
|
||||
|
|
@ -169,6 +183,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
|
||||
defp check_batch_utilization(_, _), do: :ok
|
||||
|
||||
@spec process_batch(list(map())) :: :ok
|
||||
defp process_batch(packets) do
|
||||
require Logger
|
||||
|
||||
|
|
@ -253,6 +268,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
# )
|
||||
end
|
||||
|
||||
@spec process_chunk(list(map())) :: {non_neg_integer(), non_neg_integer()}
|
||||
defp process_chunk(packets) do
|
||||
# Use Stream for memory-efficient packet preparation
|
||||
packet_stream =
|
||||
|
|
|
|||
|
|
@ -416,7 +416,6 @@ defmodule Aprsme.Packets do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_canonical_device_identifier(nil), do: nil
|
||||
defp get_canonical_device_identifier(device_identifier), do: to_string(device_identifier)
|
||||
|
||||
@doc """
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
background_size: "512px 192px"
|
||||
}
|
||||
"""
|
||||
@spec get_sprite_info(String.t() | nil, String.t() | nil) :: %{
|
||||
sprite_file: String.t(),
|
||||
background_position: String.t(),
|
||||
background_size: String.t()
|
||||
}
|
||||
def get_sprite_info(symbol_table, symbol_code) do
|
||||
# For overlay symbols (A-Z, 0-9), display the base symbol with overlay
|
||||
if symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/) do
|
||||
|
|
@ -68,6 +73,11 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
Gets sprite information for overlay symbols (A-Z, 0-9).
|
||||
These symbols display the base symbol from the overlay table.
|
||||
"""
|
||||
@spec get_overlay_base_symbol_info(String.t()) :: %{
|
||||
sprite_file: String.t(),
|
||||
background_position: String.t(),
|
||||
background_size: String.t()
|
||||
}
|
||||
def get_overlay_base_symbol_info(base_symbol_code) do
|
||||
# Some overlay base symbols are in the alternate table (1), others in overlay table (2)
|
||||
# Check which table to use based on the symbol code
|
||||
|
|
@ -101,6 +111,7 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
Determines which sprite table to use for overlay base symbols.
|
||||
Some symbols are in the alternate table (1), others in overlay table (2).
|
||||
"""
|
||||
@spec get_overlay_base_table_id(String.t()) :: String.t()
|
||||
def get_overlay_base_table_id(base_symbol_code) do
|
||||
# Map symbols to the correct sprite table based on APRS specification
|
||||
# Most overlay symbols are in the alternate table (1)
|
||||
|
|
@ -130,6 +141,11 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
Gets sprite information for overlay characters (A-Z, 0-9).
|
||||
These are rendered from the overlay table.
|
||||
"""
|
||||
@spec get_overlay_character_sprite_info(String.t()) :: %{
|
||||
sprite_file: String.t(),
|
||||
background_position: String.t(),
|
||||
background_size: String.t()
|
||||
}
|
||||
def get_overlay_character_sprite_info(overlay_char) do
|
||||
# Use overlay table (table 2) for the overlay character
|
||||
sprite_file = "/aprs-symbols/aprs-symbols-128-2@2x.png"
|
||||
|
|
@ -171,6 +187,7 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
iex> AprsmeWeb.AprsSymbol.normalize_symbol_table("invalid")
|
||||
"/"
|
||||
"""
|
||||
@spec normalize_symbol_table(String.t() | nil) :: String.t()
|
||||
def normalize_symbol_table(symbol_table) do
|
||||
cond do
|
||||
symbol_table in ["/", "\\", "]"] -> symbol_table
|
||||
|
|
@ -193,6 +210,7 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
iex> AprsmeWeb.AprsSymbol.normalize_symbol_code("")
|
||||
">"
|
||||
"""
|
||||
@spec normalize_symbol_code(String.t() | nil) :: String.t()
|
||||
def normalize_symbol_code(symbol_code) do
|
||||
if symbol_code && symbol_code != "", do: symbol_code, else: ">"
|
||||
end
|
||||
|
|
@ -211,6 +229,7 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
iex> AprsmeWeb.AprsSymbol.get_table_id("]")
|
||||
"2"
|
||||
"""
|
||||
@spec get_table_id(String.t()) :: String.t()
|
||||
def get_table_id(symbol_table) do
|
||||
case symbol_table do
|
||||
# Primary table
|
||||
|
|
@ -233,6 +252,7 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
iex> AprsmeWeb.AprsSymbol.render_marker_html("/", "_", "W1AW")
|
||||
"<div style=\"position: relative; width: 32px; height: 32px; display: flex; align-items: center;\">..."
|
||||
"""
|
||||
@spec render_marker_html(String.t() | nil, String.t() | nil, String.t() | nil, integer()) :: String.t()
|
||||
def render_marker_html(symbol_table, symbol_code, callsign \\ nil, size \\ 32) do
|
||||
# For symbols without callsigns, use Cachex for better caching
|
||||
if is_nil(callsign) do
|
||||
|
|
@ -254,6 +274,7 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
end
|
||||
end
|
||||
|
||||
@spec generate_marker_html(String.t() | nil, String.t() | nil, String.t() | nil, integer()) :: String.t()
|
||||
defp generate_marker_html(symbol_table, symbol_code, callsign, size) do
|
||||
sprite_info = get_sprite_info(symbol_table, symbol_code)
|
||||
|
||||
|
|
@ -330,6 +351,7 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
iex> AprsmeWeb.AprsSymbol.render_style("/", "_", 32)
|
||||
"width: 32px; height: 32px; background-image: url(/aprs-symbols/aprs-symbols-128-0@2x.png); ..."
|
||||
"""
|
||||
@spec render_style(String.t() | nil, String.t() | nil, integer()) :: String.t()
|
||||
def render_style(symbol_table, symbol_code, size \\ 32) do
|
||||
sprite_info = get_sprite_info(symbol_table, symbol_code)
|
||||
|
||||
|
|
@ -347,6 +369,7 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
iex> AprsmeWeb.AprsSymbol.extract_from_packet(%{})
|
||||
{"/", ">"}
|
||||
"""
|
||||
@spec extract_from_packet(map()) :: {String.t(), String.t()}
|
||||
def extract_from_packet(packet) do
|
||||
symbol_table_id = get_packet_field(packet, :symbol_table_id, "/")
|
||||
symbol_code = get_packet_field(packet, :symbol_code, ">")
|
||||
|
|
@ -355,6 +378,7 @@ defmodule AprsmeWeb.AprsSymbol do
|
|||
end
|
||||
|
||||
# Helper function to safely extract a value from a packet or data_extended map
|
||||
@spec get_packet_field(map(), atom(), any()) :: any()
|
||||
defp get_packet_field(packet, field, default) do
|
||||
data_extended = Map.get(packet, :data_extended, Map.get(packet, "data_extended", %{})) || %{}
|
||||
|
||||
|
|
|
|||
|
|
@ -232,9 +232,9 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
|
|||
value = PacketUtils.get_weather_field(packet, field)
|
||||
|
||||
case value do
|
||||
nil -> false
|
||||
"N/A" -> false
|
||||
"" -> false
|
||||
nil -> false
|
||||
_ -> true
|
||||
end
|
||||
end
|
||||
|
|
@ -246,13 +246,13 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
|
|||
value = PacketUtils.get_weather_field(packet, key)
|
||||
|
||||
case value do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
"N/A" ->
|
||||
nil
|
||||
|
||||
value when is_number(value) ->
|
||||
nil ->
|
||||
nil
|
||||
|
||||
value when is_binary(value) ->
|
||||
case @weather_formatters[key] do
|
||||
{formatter, separator} ->
|
||||
{converted_value, unit} = formatter.(value, locale)
|
||||
|
|
@ -262,7 +262,6 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
|
|||
"#{value}"
|
||||
end
|
||||
|
||||
value when is_binary(value) ->
|
||||
case @weather_formatters[key] do
|
||||
{formatter, separator} ->
|
||||
case Float.parse(value) do
|
||||
|
|
@ -278,8 +277,15 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
|
|||
"#{value}"
|
||||
end
|
||||
|
||||
_ ->
|
||||
nil
|
||||
value when is_number(value) ->
|
||||
case @weather_formatters[key] do
|
||||
{formatter, separator} ->
|
||||
{converted_value, unit} = formatter.(value, locale)
|
||||
"#{converted_value}#{separator}#{unit}"
|
||||
|
||||
nil ->
|
||||
"#{value}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,15 @@ defmodule AprsmeWeb.Plugs.RateLimiter do
|
|||
import Phoenix.Controller
|
||||
import Plug.Conn
|
||||
|
||||
@type key_type :: :ip | :user_agent | (Plug.Conn.t() -> String.t()) | String.t()
|
||||
@type init_opts :: [
|
||||
scale: integer(),
|
||||
limit: integer(),
|
||||
key: key_type(),
|
||||
error_message: String.t()
|
||||
]
|
||||
|
||||
@spec init(Keyword.t()) :: init_opts()
|
||||
def init(opts) do
|
||||
# Default options
|
||||
Keyword.merge(
|
||||
|
|
@ -21,6 +30,7 @@ defmodule AprsmeWeb.Plugs.RateLimiter do
|
|||
)
|
||||
end
|
||||
|
||||
@spec call(Plug.Conn.t(), init_opts()) :: Plug.Conn.t()
|
||||
def call(conn, opts) do
|
||||
key = get_key(conn, opts[:key])
|
||||
scale = opts[:scale]
|
||||
|
|
@ -39,6 +49,7 @@ defmodule AprsmeWeb.Plugs.RateLimiter do
|
|||
end
|
||||
end
|
||||
|
||||
@spec get_key(Plug.Conn.t(), key_type()) :: String.t()
|
||||
defp get_key(conn, :ip) do
|
||||
# Check headers in order of preference
|
||||
case {get_req_header(conn, "cf-connecting-ip"), get_req_header(conn, "x-forwarded-for"),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ defmodule AprsmeWeb.TimeHelpers do
|
|||
format_time_diff(diff_seconds) <> " " <> gettext("ago")
|
||||
end
|
||||
|
||||
@spec format_time_diff(integer()) :: String.t()
|
||||
defp format_time_diff(seconds) when seconds < 60, do: gettext("less than a minute")
|
||||
defp format_time_diff(seconds) when seconds < 120, do: gettext("1 minute")
|
||||
defp format_time_diff(seconds) when seconds < 3600, do: gettext("%{count} minutes", count: div(seconds, 60))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ defmodule AprsmeWeb.TimeUtils do
|
|||
@doc """
|
||||
Returns a DateTime that is the specified number of hours before now.
|
||||
"""
|
||||
@spec hours_ago(number()) :: DateTime.t()
|
||||
def hours_ago(hours) when is_number(hours) do
|
||||
DateTime.add(DateTime.utc_now(), -hours * 3600, :second)
|
||||
end
|
||||
|
|
@ -13,33 +14,41 @@ defmodule AprsmeWeb.TimeUtils do
|
|||
@doc """
|
||||
Returns a DateTime that is one hour before now.
|
||||
"""
|
||||
@spec one_hour_ago() :: DateTime.t()
|
||||
def one_hour_ago, do: hours_ago(1)
|
||||
|
||||
@doc """
|
||||
Returns a DateTime that is 24 hours (one day) before now.
|
||||
"""
|
||||
@spec one_day_ago() :: DateTime.t()
|
||||
def one_day_ago, do: hours_ago(24)
|
||||
|
||||
@doc """
|
||||
Returns a DateTime that is 48 hours (two days) before now.
|
||||
"""
|
||||
@spec two_days_ago() :: DateTime.t()
|
||||
def two_days_ago, do: hours_ago(48)
|
||||
|
||||
@doc """
|
||||
Returns a DateTime that is 7 days (one week) before now.
|
||||
"""
|
||||
@spec one_week_ago() :: DateTime.t()
|
||||
def one_week_ago, do: hours_ago(24 * 7)
|
||||
|
||||
@doc """
|
||||
Returns a DateTime that is the specified number of days before now.
|
||||
"""
|
||||
@spec days_ago(number()) :: DateTime.t()
|
||||
def days_ago(days) when is_number(days) do
|
||||
DateTime.add(DateTime.utc_now(), -days, :day)
|
||||
end
|
||||
|
||||
@type time_range_atom :: :last_hour | :last_day | :last_two_days | :last_week
|
||||
|
||||
@doc """
|
||||
Returns a tuple of {start_time, end_time} for common time ranges.
|
||||
"""
|
||||
@spec time_range(time_range_atom()) :: {DateTime.t(), DateTime.t()}
|
||||
def time_range(:last_hour), do: {one_hour_ago(), DateTime.utc_now()}
|
||||
def time_range(:last_day), do: {one_day_ago(), DateTime.utc_now()}
|
||||
def time_range(:last_two_days), do: {two_days_ago(), DateTime.utc_now()}
|
||||
|
|
@ -48,5 +57,6 @@ defmodule AprsmeWeb.TimeUtils do
|
|||
@doc """
|
||||
Returns a default time range of 48 hours.
|
||||
"""
|
||||
@spec default_time_range() :: {DateTime.t(), DateTime.t()}
|
||||
def default_time_range, do: time_range(:last_two_days)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ defmodule AprsmeWeb.UserAuth do
|
|||
|> redirect(to: user_return_to || signed_in_path(conn))
|
||||
end
|
||||
|
||||
@spec maybe_write_remember_me_cookie(Plug.Conn.t(), String.t(), map()) :: Plug.Conn.t()
|
||||
defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do
|
||||
put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options)
|
||||
end
|
||||
|
|
@ -61,6 +62,7 @@ defmodule AprsmeWeb.UserAuth do
|
|||
# |> put_session(:preferred_locale, preferred_locale)
|
||||
# end
|
||||
#
|
||||
@spec renew_session(Plug.Conn.t()) :: Plug.Conn.t()
|
||||
defp renew_session(conn) do
|
||||
conn
|
||||
|> configure_session(renew: true)
|
||||
|
|
@ -98,6 +100,7 @@ defmodule AprsmeWeb.UserAuth do
|
|||
assign(conn, :current_user, user)
|
||||
end
|
||||
|
||||
@spec ensure_user_token(Plug.Conn.t()) :: {String.t() | nil, Plug.Conn.t()}
|
||||
defp ensure_user_token(conn) do
|
||||
case get_session(conn, :user_token) do
|
||||
nil -> ensure_user_token_from_cookie(conn)
|
||||
|
|
@ -105,6 +108,7 @@ defmodule AprsmeWeb.UserAuth do
|
|||
end
|
||||
end
|
||||
|
||||
@spec ensure_user_token_from_cookie(Plug.Conn.t()) :: {String.t() | nil, Plug.Conn.t()}
|
||||
defp ensure_user_token_from_cookie(conn) do
|
||||
conn = fetch_cookies(conn, signed: [@remember_me_cookie])
|
||||
|
||||
|
|
@ -180,6 +184,7 @@ defmodule AprsmeWeb.UserAuth do
|
|||
end
|
||||
end
|
||||
|
||||
@spec mount_current_user(map(), Socket.t()) :: Socket.t()
|
||||
defp mount_current_user(session, socket) do
|
||||
case session do
|
||||
%{"user_token" => user_token} ->
|
||||
|
|
@ -229,17 +234,20 @@ defmodule AprsmeWeb.UserAuth do
|
|||
end
|
||||
end
|
||||
|
||||
@spec put_token_in_session(Plug.Conn.t(), String.t()) :: Plug.Conn.t()
|
||||
defp put_token_in_session(conn, token) do
|
||||
conn
|
||||
|> put_session(:user_token, token)
|
||||
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|
||||
end
|
||||
|
||||
@spec maybe_store_return_to(Plug.Conn.t()) :: Plug.Conn.t()
|
||||
defp maybe_store_return_to(%{method: "GET"} = conn) do
|
||||
put_session(conn, :user_return_to, current_path(conn))
|
||||
end
|
||||
|
||||
defp maybe_store_return_to(conn), do: conn
|
||||
|
||||
@spec signed_in_path(Plug.Conn.t() | Socket.t()) :: String.t()
|
||||
defp signed_in_path(_conn), do: ~p"/"
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue