improve dialyzer

This commit is contained in:
Graham McIntire 2025-10-13 10:02:15 -05:00
parent d75896597e
commit 751b8a5f1b
No known key found for this signature in database
11 changed files with 96 additions and 16 deletions

View file

@ -13,5 +13,14 @@
# IP geolocation plug - these patterns are actually reachable but dialyzer can't prove it # 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 # 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"}
] ]

View file

@ -1,3 +1,3 @@
elixir 1.19.0-rc.0-otp-27 elixir 1.19.0-rc.2-otp-27
erlang 27.3.4 erlang 27.3.4
gleam 1.11.1 gleam 1.11.1

View file

@ -512,7 +512,7 @@ defmodule Aprsme.Is do
attrs = Aprsme.Packet.extract_additional_data(attrs, message) attrs = Aprsme.Packet.extract_additional_data(attrs, message)
# Normalize data_type to string if it's an atom # 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 # Submit to GenStage pipeline for batch processing
Aprsme.PacketProducer.submit_packet(attrs) Aprsme.PacketProducer.submit_packet(attrs)
@ -542,10 +542,6 @@ defmodule Aprsme.Is do
end end
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() @spec update_packet_stats(map(), integer()) :: map()
defp update_packet_stats(stats, current_time) do defp update_packet_stats(stats, current_time) do
new_total = stats.total_packets + 1 new_total = stats.total_packets + 1

View file

@ -11,6 +11,15 @@ defmodule Aprsme.PacketConsumer do
require Logger 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 def start_link(opts \\ []) do
# Allow unnamed consumers for pool usage # Allow unnamed consumers for pool usage
name = opts[:name] name = opts[:name]
@ -102,6 +111,7 @@ defmodule Aprsme.PacketConsumer do
end end
# Helper functions with pattern matching # Helper functions with pattern matching
@spec reset_batch_timer(state()) :: state()
defp reset_batch_timer(%{timer: nil} = state) do defp reset_batch_timer(%{timer: nil} = state) do
new_timer = Process.send_after(self(), :process_batch, state.batch_timeout) new_timer = Process.send_after(self(), :process_batch, state.batch_timeout)
%{state | batch: [], timer: new_timer} %{state | batch: [], timer: new_timer}
@ -114,10 +124,12 @@ defmodule Aprsme.PacketConsumer do
end end
# Efficient batch length calculation (cached if possible) # Efficient batch length calculation (cached if possible)
@spec batch_length(list()) :: non_neg_integer()
defp batch_length([]), do: 0 defp batch_length([]), do: 0
defp batch_length(batch), do: length(batch) defp batch_length(batch), do: length(batch)
# Pattern matching for logging # Pattern matching for logging
@spec log_dropped_packets(list(), list()) :: :ok
defp log_dropped_packets([], _), do: :ok defp log_dropped_packets([], _), do: :ok
defp log_dropped_packets(dropped, processed) do defp log_dropped_packets(dropped, processed) do
@ -150,12 +162,14 @@ defmodule Aprsme.PacketConsumer do
end end
# Helper to start batch timer # Helper to start batch timer
@spec start_batch_timer(state()) :: state()
defp start_batch_timer(%{batch_timeout: timeout} = state) do defp start_batch_timer(%{batch_timeout: timeout} = state) do
timer = Process.send_after(self(), :process_batch, timeout) timer = Process.send_after(self(), :process_batch, timeout)
%{state | timer: timer} %{state | timer: timer}
end end
# Pattern matching for batch utilization check # 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 defp check_batch_utilization(batch, max_size) when length(batch) > max_size * 0.8 do
Logger.warning("Batch size approaching limit", Logger.warning("Batch size approaching limit",
batch_status: batch_status:
@ -169,6 +183,7 @@ defmodule Aprsme.PacketConsumer do
defp check_batch_utilization(_, _), do: :ok defp check_batch_utilization(_, _), do: :ok
@spec process_batch(list(map())) :: :ok
defp process_batch(packets) do defp process_batch(packets) do
require Logger require Logger
@ -253,6 +268,7 @@ defmodule Aprsme.PacketConsumer do
# ) # )
end end
@spec process_chunk(list(map())) :: {non_neg_integer(), non_neg_integer()}
defp process_chunk(packets) do defp process_chunk(packets) do
# Use Stream for memory-efficient packet preparation # Use Stream for memory-efficient packet preparation
packet_stream = packet_stream =

View file

@ -416,7 +416,6 @@ defmodule Aprsme.Packets do
end end
end end
defp get_canonical_device_identifier(nil), do: nil
defp get_canonical_device_identifier(device_identifier), do: to_string(device_identifier) defp get_canonical_device_identifier(device_identifier), do: to_string(device_identifier)
@doc """ @doc """

View file

@ -25,6 +25,11 @@ defmodule AprsmeWeb.AprsSymbol do
background_size: "512px 192px" 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 def get_sprite_info(symbol_table, symbol_code) do
# For overlay symbols (A-Z, 0-9), display the base symbol with overlay # 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 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). Gets sprite information for overlay symbols (A-Z, 0-9).
These symbols display the base symbol from the overlay table. 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 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) # 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 # 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. Determines which sprite table to use for overlay base symbols.
Some symbols are in the alternate table (1), others in overlay table (2). 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 def get_overlay_base_table_id(base_symbol_code) do
# Map symbols to the correct sprite table based on APRS specification # Map symbols to the correct sprite table based on APRS specification
# Most overlay symbols are in the alternate table (1) # 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). Gets sprite information for overlay characters (A-Z, 0-9).
These are rendered from the overlay table. 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 def get_overlay_character_sprite_info(overlay_char) do
# Use overlay table (table 2) for the overlay character # Use overlay table (table 2) for the overlay character
sprite_file = "/aprs-symbols/aprs-symbols-128-2@2x.png" 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") iex> AprsmeWeb.AprsSymbol.normalize_symbol_table("invalid")
"/" "/"
""" """
@spec normalize_symbol_table(String.t() | nil) :: String.t()
def normalize_symbol_table(symbol_table) do def normalize_symbol_table(symbol_table) do
cond do cond do
symbol_table in ["/", "\\", "]"] -> symbol_table symbol_table in ["/", "\\", "]"] -> symbol_table
@ -193,6 +210,7 @@ defmodule AprsmeWeb.AprsSymbol do
iex> AprsmeWeb.AprsSymbol.normalize_symbol_code("") iex> AprsmeWeb.AprsSymbol.normalize_symbol_code("")
">" ">"
""" """
@spec normalize_symbol_code(String.t() | nil) :: String.t()
def normalize_symbol_code(symbol_code) do def normalize_symbol_code(symbol_code) do
if symbol_code && symbol_code != "", do: symbol_code, else: ">" if symbol_code && symbol_code != "", do: symbol_code, else: ">"
end end
@ -211,6 +229,7 @@ defmodule AprsmeWeb.AprsSymbol do
iex> AprsmeWeb.AprsSymbol.get_table_id("]") iex> AprsmeWeb.AprsSymbol.get_table_id("]")
"2" "2"
""" """
@spec get_table_id(String.t()) :: String.t()
def get_table_id(symbol_table) do def get_table_id(symbol_table) do
case symbol_table do case symbol_table do
# Primary table # Primary table
@ -233,6 +252,7 @@ defmodule AprsmeWeb.AprsSymbol do
iex> AprsmeWeb.AprsSymbol.render_marker_html("/", "_", "W1AW") iex> AprsmeWeb.AprsSymbol.render_marker_html("/", "_", "W1AW")
"<div style=\"position: relative; width: 32px; height: 32px; display: flex; align-items: center;\">..." "<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 def render_marker_html(symbol_table, symbol_code, callsign \\ nil, size \\ 32) do
# For symbols without callsigns, use Cachex for better caching # For symbols without callsigns, use Cachex for better caching
if is_nil(callsign) do if is_nil(callsign) do
@ -254,6 +274,7 @@ defmodule AprsmeWeb.AprsSymbol do
end end
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 defp generate_marker_html(symbol_table, symbol_code, callsign, size) do
sprite_info = get_sprite_info(symbol_table, symbol_code) sprite_info = get_sprite_info(symbol_table, symbol_code)
@ -330,6 +351,7 @@ defmodule AprsmeWeb.AprsSymbol do
iex> AprsmeWeb.AprsSymbol.render_style("/", "_", 32) iex> AprsmeWeb.AprsSymbol.render_style("/", "_", 32)
"width: 32px; height: 32px; background-image: url(/aprs-symbols/aprs-symbols-128-0@2x.png); ..." "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 def render_style(symbol_table, symbol_code, size \\ 32) do
sprite_info = get_sprite_info(symbol_table, symbol_code) sprite_info = get_sprite_info(symbol_table, symbol_code)
@ -347,6 +369,7 @@ defmodule AprsmeWeb.AprsSymbol do
iex> AprsmeWeb.AprsSymbol.extract_from_packet(%{}) iex> AprsmeWeb.AprsSymbol.extract_from_packet(%{})
{"/", ">"} {"/", ">"}
""" """
@spec extract_from_packet(map()) :: {String.t(), String.t()}
def extract_from_packet(packet) do def extract_from_packet(packet) do
symbol_table_id = get_packet_field(packet, :symbol_table_id, "/") symbol_table_id = get_packet_field(packet, :symbol_table_id, "/")
symbol_code = get_packet_field(packet, :symbol_code, ">") symbol_code = get_packet_field(packet, :symbol_code, ">")
@ -355,6 +378,7 @@ defmodule AprsmeWeb.AprsSymbol do
end end
# Helper function to safely extract a value from a packet or data_extended map # 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 defp get_packet_field(packet, field, default) do
data_extended = Map.get(packet, :data_extended, Map.get(packet, "data_extended", %{})) || %{} data_extended = Map.get(packet, :data_extended, Map.get(packet, "data_extended", %{})) || %{}

View file

@ -232,9 +232,9 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
value = PacketUtils.get_weather_field(packet, field) value = PacketUtils.get_weather_field(packet, field)
case value do case value do
nil -> false
"N/A" -> false "N/A" -> false
"" -> false "" -> false
nil -> false
_ -> true _ -> true
end end
end end
@ -246,13 +246,13 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
value = PacketUtils.get_weather_field(packet, key) value = PacketUtils.get_weather_field(packet, key)
case value do case value do
nil ->
nil
"N/A" -> "N/A" ->
nil nil
value when is_number(value) -> nil ->
nil
value when is_binary(value) ->
case @weather_formatters[key] do case @weather_formatters[key] do
{formatter, separator} -> {formatter, separator} ->
{converted_value, unit} = formatter.(value, locale) {converted_value, unit} = formatter.(value, locale)
@ -262,7 +262,6 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
"#{value}" "#{value}"
end end
value when is_binary(value) ->
case @weather_formatters[key] do case @weather_formatters[key] do
{formatter, separator} -> {formatter, separator} ->
case Float.parse(value) do case Float.parse(value) do
@ -278,8 +277,15 @@ defmodule AprsmeWeb.WeatherLive.CallsignView do
"#{value}" "#{value}"
end end
_ -> value when is_number(value) ->
nil case @weather_formatters[key] do
{formatter, separator} ->
{converted_value, unit} = formatter.(value, locale)
"#{converted_value}#{separator}#{unit}"
nil ->
"#{value}"
end
end end
end end
end end

View file

@ -5,6 +5,15 @@ defmodule AprsmeWeb.Plugs.RateLimiter do
import Phoenix.Controller import Phoenix.Controller
import Plug.Conn 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 def init(opts) do
# Default options # Default options
Keyword.merge( Keyword.merge(
@ -21,6 +30,7 @@ defmodule AprsmeWeb.Plugs.RateLimiter do
) )
end end
@spec call(Plug.Conn.t(), init_opts()) :: Plug.Conn.t()
def call(conn, opts) do def call(conn, opts) do
key = get_key(conn, opts[:key]) key = get_key(conn, opts[:key])
scale = opts[:scale] scale = opts[:scale]
@ -39,6 +49,7 @@ defmodule AprsmeWeb.Plugs.RateLimiter do
end end
end end
@spec get_key(Plug.Conn.t(), key_type()) :: String.t()
defp get_key(conn, :ip) do defp get_key(conn, :ip) do
# Check headers in order of preference # Check headers in order of preference
case {get_req_header(conn, "cf-connecting-ip"), get_req_header(conn, "x-forwarded-for"), case {get_req_header(conn, "cf-connecting-ip"), get_req_header(conn, "x-forwarded-for"),

View file

@ -23,6 +23,7 @@ defmodule AprsmeWeb.TimeHelpers do
format_time_diff(diff_seconds) <> " " <> gettext("ago") format_time_diff(diff_seconds) <> " " <> gettext("ago")
end 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 < 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 < 120, do: gettext("1 minute")
defp format_time_diff(seconds) when seconds < 3600, do: gettext("%{count} minutes", count: div(seconds, 60)) defp format_time_diff(seconds) when seconds < 3600, do: gettext("%{count} minutes", count: div(seconds, 60))

View file

@ -6,6 +6,7 @@ defmodule AprsmeWeb.TimeUtils do
@doc """ @doc """
Returns a DateTime that is the specified number of hours before now. 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 def hours_ago(hours) when is_number(hours) do
DateTime.add(DateTime.utc_now(), -hours * 3600, :second) DateTime.add(DateTime.utc_now(), -hours * 3600, :second)
end end
@ -13,33 +14,41 @@ defmodule AprsmeWeb.TimeUtils do
@doc """ @doc """
Returns a DateTime that is one hour before now. Returns a DateTime that is one hour before now.
""" """
@spec one_hour_ago() :: DateTime.t()
def one_hour_ago, do: hours_ago(1) def one_hour_ago, do: hours_ago(1)
@doc """ @doc """
Returns a DateTime that is 24 hours (one day) before now. 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) def one_day_ago, do: hours_ago(24)
@doc """ @doc """
Returns a DateTime that is 48 hours (two days) before now. 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) def two_days_ago, do: hours_ago(48)
@doc """ @doc """
Returns a DateTime that is 7 days (one week) before now. 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) def one_week_ago, do: hours_ago(24 * 7)
@doc """ @doc """
Returns a DateTime that is the specified number of days before now. 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 def days_ago(days) when is_number(days) do
DateTime.add(DateTime.utc_now(), -days, :day) DateTime.add(DateTime.utc_now(), -days, :day)
end end
@type time_range_atom :: :last_hour | :last_day | :last_two_days | :last_week
@doc """ @doc """
Returns a tuple of {start_time, end_time} for common time ranges. 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_hour), do: {one_hour_ago(), DateTime.utc_now()}
def time_range(:last_day), do: {one_day_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()} def time_range(:last_two_days), do: {two_days_ago(), DateTime.utc_now()}
@ -48,5 +57,6 @@ defmodule AprsmeWeb.TimeUtils do
@doc """ @doc """
Returns a default time range of 48 hours. 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) def default_time_range, do: time_range(:last_two_days)
end end

View file

@ -40,6 +40,7 @@ defmodule AprsmeWeb.UserAuth do
|> redirect(to: user_return_to || signed_in_path(conn)) |> redirect(to: user_return_to || signed_in_path(conn))
end 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 defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do
put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options) put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options)
end end
@ -61,6 +62,7 @@ defmodule AprsmeWeb.UserAuth do
# |> put_session(:preferred_locale, preferred_locale) # |> put_session(:preferred_locale, preferred_locale)
# end # end
# #
@spec renew_session(Plug.Conn.t()) :: Plug.Conn.t()
defp renew_session(conn) do defp renew_session(conn) do
conn conn
|> configure_session(renew: true) |> configure_session(renew: true)
@ -98,6 +100,7 @@ defmodule AprsmeWeb.UserAuth do
assign(conn, :current_user, user) assign(conn, :current_user, user)
end end
@spec ensure_user_token(Plug.Conn.t()) :: {String.t() | nil, Plug.Conn.t()}
defp ensure_user_token(conn) do defp ensure_user_token(conn) do
case get_session(conn, :user_token) do case get_session(conn, :user_token) do
nil -> ensure_user_token_from_cookie(conn) nil -> ensure_user_token_from_cookie(conn)
@ -105,6 +108,7 @@ defmodule AprsmeWeb.UserAuth do
end end
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 defp ensure_user_token_from_cookie(conn) do
conn = fetch_cookies(conn, signed: [@remember_me_cookie]) conn = fetch_cookies(conn, signed: [@remember_me_cookie])
@ -180,6 +184,7 @@ defmodule AprsmeWeb.UserAuth do
end end
end end
@spec mount_current_user(map(), Socket.t()) :: Socket.t()
defp mount_current_user(session, socket) do defp mount_current_user(session, socket) do
case session do case session do
%{"user_token" => user_token} -> %{"user_token" => user_token} ->
@ -229,17 +234,20 @@ defmodule AprsmeWeb.UserAuth do
end end
end end
@spec put_token_in_session(Plug.Conn.t(), String.t()) :: Plug.Conn.t()
defp put_token_in_session(conn, token) do defp put_token_in_session(conn, token) do
conn conn
|> put_session(:user_token, token) |> put_session(:user_token, token)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}") |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
end end
@spec maybe_store_return_to(Plug.Conn.t()) :: Plug.Conn.t()
defp maybe_store_return_to(%{method: "GET"} = conn) do defp maybe_store_return_to(%{method: "GET"} = conn) do
put_session(conn, :user_return_to, current_path(conn)) put_session(conn, :user_return_to, current_path(conn))
end end
defp maybe_store_return_to(conn), do: conn 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"/" defp signed_in_path(_conn), do: ~p"/"
end end