refactor: Reduce function complexity and nesting depth

- Extract helper functions from process_chunk to reduce nesting
- Extract format_error_message from extract_changeset_error
- Split extract_error_message into smaller functions to reduce complexity
- Fixes Credo refactoring opportunities
This commit is contained in:
Moritz 2026-01-20 16:05:09 +01:00 committed by Simon
parent c137ee6221
commit 4e48ace2d4
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
2 changed files with 107 additions and 55 deletions

View file

@ -302,28 +302,15 @@ defmodule Mv.Membership.Import.MemberCSV do
max_errors = Keyword.get(opts, :max_errors, 50) max_errors = Keyword.get(opts, :max_errors, 50)
{inserted, failed, errors, _collected_error_count, truncated?} = {inserted, failed, errors, _collected_error_count, truncated?} =
Enum.reduce(chunk_rows_with_lines, {0, 0, [], 0, false}, fn {line_number, row_map}, Enum.reduce(chunk_rows_with_lines, {0, 0, [], 0, false}, fn {line_number, row_map}, acc ->
{acc_inserted, acc_failed, current_error_count = existing_error_count + elem(acc, 3)
acc_errors, acc_error_count,
acc_truncated?} ->
current_error_count = existing_error_count + acc_error_count
case process_row(row_map, line_number, custom_field_lookup) do case process_row(row_map, line_number, custom_field_lookup) do
{:ok, _member} -> {:ok, _member} ->
{acc_inserted + 1, acc_failed, acc_errors, acc_error_count, acc_truncated?} update_inserted(acc)
{:error, error} -> {:error, error} ->
new_acc_failed = acc_failed + 1 handle_row_error(acc, error, current_error_count, max_errors)
# Only collect errors if under limit
{new_acc_errors, new_error_count, new_truncated?} =
if current_error_count < max_errors do
{[error | acc_errors], acc_error_count + 1, acc_truncated?}
else
{acc_errors, acc_error_count, true}
end
{acc_inserted, new_acc_failed, new_acc_errors, new_error_count, new_truncated?}
end end
end) end)
@ -397,11 +384,9 @@ defmodule Mv.Membership.Import.MemberCSV do
# Extracts the first error from a changeset and converts it to a MemberCSV.Error struct # Extracts the first error from a changeset and converts it to a MemberCSV.Error struct
defp extract_changeset_error(changeset, csv_line_number) do defp extract_changeset_error(changeset, csv_line_number) do
case Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} -> errors = Ecto.Changeset.traverse_errors(changeset, &format_error_message/1)
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value)) case errors do
end)
end) do
%{email: [message | _]} -> %{email: [message | _]} ->
# Email-specific error # Email-specific error
%Error{ %Error{
@ -430,6 +415,56 @@ defmodule Mv.Membership.Import.MemberCSV do
end end
end end
# Helper function to update accumulator when row is successfully inserted
defp update_inserted({acc_inserted, acc_failed, acc_errors, acc_error_count, acc_truncated?}) do
{acc_inserted + 1, acc_failed, acc_errors, acc_error_count, acc_truncated?}
end
# Helper function to handle row error with error count limit checking
defp handle_row_error(
{acc_inserted, acc_failed, acc_errors, acc_error_count, acc_truncated?},
error,
current_error_count,
max_errors
) do
new_acc_failed = acc_failed + 1
{new_acc_errors, new_error_count, new_truncated?} =
collect_error_if_under_limit(
error,
acc_errors,
acc_error_count,
acc_truncated?,
current_error_count,
max_errors
)
{acc_inserted, new_acc_failed, new_acc_errors, new_error_count, new_truncated?}
end
# Helper function to collect error only if under limit
defp collect_error_if_under_limit(
error,
acc_errors,
acc_error_count,
acc_truncated?,
current_error_count,
max_errors
) do
if current_error_count < max_errors do
{[error | acc_errors], acc_error_count + 1, acc_truncated?}
else
{acc_errors, acc_error_count, true}
end
end
# Formats error message by replacing placeholders
defp format_error_message({msg, opts}) do
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end
# Maps changeset error messages to appropriate Gettext messages # Maps changeset error messages to appropriate Gettext messages
defp gettext_error_message(message) when is_binary(message) do defp gettext_error_message(message) when is_binary(message) do
cond do cond do

View file

@ -355,48 +355,65 @@ defmodule MvWeb.MemberLive.Form do
# Extracts a user-friendly error message from form errors # Extracts a user-friendly error message from form errors
defp extract_error_message(form) do defp extract_error_message(form) do
# Try to extract message from source errors first
source_errors = get_source_errors(form) source_errors = get_source_errors(form)
case source_errors do cond do
[%Ash.Error.Invalid{errors: errors} | _] when is_list(errors) -> has_invalid_error?(source_errors) ->
# Extract first error message extract_invalid_error_message(source_errors)
case List.first(errors) do
%{message: message} when is_binary(message) ->
gettext("Validation failed: %{message}", message: message)
%{field: field, message: message} when is_binary(message) -> has_other_error?(source_errors) ->
gettext("Validation failed: %{field} %{message}", field: field, message: message) extract_other_error_message(source_errors)
_ -> has_form_errors?(form) ->
gettext("Validation failed. Please check your input.") gettext("Please correct the errors in the form and try again.")
end
[error | _] -> true ->
# Try to extract message from other error types gettext("Failed to save member. Please try again.")
case error do end
%{message: message} when is_binary(message) -> end
message
error when is_struct(error) -> # Checks if source errors contain an Ash.Error.Invalid
# Try to use Ash.ErrorKind protocol if available defp has_invalid_error?([%Ash.Error.Invalid{errors: errors} | _]) when is_list(errors), do: true
try do defp has_invalid_error?(_), do: false
Ash.ErrorKind.message(error)
rescue
Protocol.UndefinedError -> gettext("Failed to save member. Please try again.")
end
_ -> # Extracts message from Ash.Error.Invalid
gettext("Failed to save member. Please try again.") defp extract_invalid_error_message([%Ash.Error.Invalid{errors: errors} | _]) do
end case List.first(errors) do
%{message: message} when is_binary(message) ->
gettext("Validation failed: %{message}", message: message)
%{field: field, message: message} when is_binary(message) ->
gettext("Validation failed: %{field} %{message}", field: field, message: message)
_ -> _ ->
# Check if there are any field errors in the form gettext("Validation failed. Please check your input.")
if has_form_errors?(form) do end
gettext("Please correct the errors in the form and try again.") end
else
gettext("Failed to save member. Please try again.") # Checks if source errors contain other error types
end defp has_other_error?([_ | _]), do: true
defp has_other_error?(_), do: false
# Extracts message from other error types
defp extract_other_error_message([error | _]) do
cond do
Map.has_key?(error, :message) and is_binary(error.message) ->
error.message
is_struct(error) ->
extract_struct_error_message(error)
true ->
gettext("Failed to save member. Please try again.")
end
end
# Extracts message from struct error using Ash.ErrorKind protocol
defp extract_struct_error_message(error) do
try do
Ash.ErrorKind.message(error)
rescue
Protocol.UndefinedError -> gettext("Failed to save member. Please try again.")
end end
end end