Replace inspect(reason) with friendly error messages

Closes #81
This commit is contained in:
Claudio Ortolina
2026-03-05 20:25:13 +00:00
parent e8f393c5a0
commit 08f7565d44
18 changed files with 542 additions and 64 deletions
+1
View File
@@ -279,6 +279,7 @@ All authenticated routes live inside a single `live_session` with three `on_moun
| Module | Purpose | | Module | Purpose |
|--------|---------| |--------|---------|
| `ErrorMessages` | Maps internal error terms (atoms, structs) to user-friendly gettext strings via `friendly_message/1` |
| `Markdown` | Markdown-to-HTML conversion with `[[double bracket]]` link syntax | | `Markdown` | Markdown-to-HTML conversion with `[[double bracket]]` link syntax |
| `Duration` | Milliseconds to human-readable duration formatting | | `Duration` | Milliseconds to human-readable duration formatting |
| `LiveHelpers.Params` | Pagination param parsing from URL query params | | `LiveHelpers.Params` | Pagination param parsing from URL query params |
+1
View File
@@ -51,6 +51,7 @@ Rules extracted from commit history that are specific to this project and not al
## Error Handling ## Error Handling
- **Toast notifications:** `put_toast/3` (arity 3) in LiveViews, `put_toast!/2` (arity 2) in LiveComponents. `:info` for success, `:error` for failures. - **Toast notifications:** `put_toast/3` (arity 3) in LiveViews, `put_toast!/2` (arity 2) in LiveComponents. `:info` for success, `:error` for failures.
- **User-facing error reasons use `ErrorMessages.friendly_message/1`** — never `inspect(reason)`. Call sites keep their contextual prefix (e.g. `gettext("Error refreshing cover")`) and append `": " <> ErrorMessages.friendly_message(reason)` for the reason part. `Logger.error` calls keep `inspect` for debugging.
- **`handle_async` always handles three cases:** `{:ok, {:ok, result}}`, `{:ok, {:error, reason}}`, and `{:exit, reason}`. - **`handle_async` always handles three cases:** `{:ok, {:ok, result}}`, `{:ok, {:error, reason}}`, and `{:exit, reason}`.
- **Data cascade on upstream changes:** When artist metadata changes, regenerate dependent record embeddings. - **Data cascade on upstream changes:** When artist metadata changes, regenerate dependent record embeddings.
@@ -4,6 +4,7 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do
alias MusicBrainz.ReleaseGroupSearchResult alias MusicBrainz.ReleaseGroupSearchResult
alias MusicLibrary.BarcodeScan alias MusicLibrary.BarcodeScan
alias MusicLibrary.Records alias MusicLibrary.Records
alias MusicLibraryWeb.ErrorMessages
alias MusicLibraryWeb.RecordComponents alias MusicLibraryWeb.RecordComponents
require Logger require Logger
@@ -410,7 +411,7 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do
errors -> errors ->
errors_summary = errors_summary =
Enum.map_join(errors, "\n", fn {number, reason} -> Enum.map_join(errors, "\n", fn {number, reason} ->
"#{number}: #{inspect(reason)}" "#{number}: #{ErrorMessages.friendly_message(reason)}"
end) end)
put_toast( put_toast(
@@ -7,6 +7,7 @@ defmodule MusicLibraryWeb.Components.RecordForm do
alias MusicLibrary.{Assets, Records} alias MusicLibrary.{Assets, Records}
alias MusicLibrary.Assets.Image alias MusicLibrary.Assets.Image
alias MusicLibrary.Records.Record alias MusicLibrary.Records.Record
alias MusicLibraryWeb.ErrorMessages
@impl true @impl true
def mount(socket) do def mount(socket) do
@@ -538,14 +539,20 @@ defmodule MusicLibraryWeb.Components.RecordForm do
def handle_async(:cover_search, {:ok, {:error, reason}}, socket) do def handle_async(:cover_search, {:ok, {:error, reason}}, socket) do
{:noreply, {:noreply,
socket socket
|> assign(:cover_search_error, "Search failed: #{inspect(reason)}") |> assign(
:cover_search_error,
gettext("Search failed") <> ": " <> ErrorMessages.friendly_message(reason)
)
|> assign(:cover_search_loading, false)} |> assign(:cover_search_loading, false)}
end end
def handle_async(:cover_search, {:exit, reason}, socket) do def handle_async(:cover_search, {:exit, reason}, socket) do
{:noreply, {:noreply,
socket socket
|> assign(:cover_search_error, "Search failed: #{inspect(reason)}") |> assign(
:cover_search_error,
gettext("Search failed") <> ": " <> ErrorMessages.friendly_message(reason)
)
|> assign(:cover_search_loading, false)} |> assign(:cover_search_loading, false)}
end end
@@ -571,14 +578,20 @@ defmodule MusicLibraryWeb.Components.RecordForm do
def handle_async(:cover_download, {:ok, {:error, reason}}, socket) do def handle_async(:cover_download, {:ok, {:error, reason}}, socket) do
{:noreply, {:noreply,
socket socket
|> assign(:cover_search_error, "Download failed: #{inspect(reason)}") |> assign(
:cover_search_error,
gettext("Download failed") <> ": " <> ErrorMessages.friendly_message(reason)
)
|> assign(:cover_search_loading, false)} |> assign(:cover_search_loading, false)}
end end
def handle_async(:cover_download, {:exit, reason}, socket) do def handle_async(:cover_download, {:exit, reason}, socket) do
{:noreply, {:noreply,
socket socket
|> assign(:cover_search_error, "Download failed: #{inspect(reason)}") |> assign(
:cover_search_error,
gettext("Download failed") <> ": " <> ErrorMessages.friendly_message(reason)
)
|> assign(:cover_search_loading, false)} |> assign(:cover_search_loading, false)}
end end
+5 -3
View File
@@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.Components.Release do
alias MusicBrainz.Release alias MusicBrainz.Release
alias MusicLibrary.ScrobbleActivity alias MusicLibrary.ScrobbleActivity
alias MusicLibraryWeb.Duration alias MusicLibraryWeb.Duration
alias MusicLibraryWeb.ErrorMessages
alias Phoenix.LiveView.AsyncResult alias Phoenix.LiveView.AsyncResult
def open(id), do: Fluxon.open_dialog(id) def open(id), do: Fluxon.open_dialog(id)
@@ -267,7 +268,7 @@ defmodule MusicLibraryWeb.Components.Release do
put_toast!( put_toast!(
:error, :error,
gettext("Error scrobbling release") <> "," <> inspect(reason) gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason)
) )
{:noreply, socket} {:noreply, socket}
@@ -298,7 +299,7 @@ defmodule MusicLibraryWeb.Components.Release do
put_toast!( put_toast!(
:error, :error,
gettext("Error scrobbling disc") <> "," <> inspect(reason) gettext("Error scrobbling disc") <> ": " <> ErrorMessages.friendly_message(reason)
) )
{:noreply, socket} {:noreply, socket}
@@ -372,7 +373,8 @@ defmodule MusicLibraryWeb.Components.Release do
put_toast!( put_toast!(
:error, :error,
gettext("Error scrobbling selected tracks") <> "," <> inspect(reason) gettext("Error scrobbling selected tracks") <>
": " <> ErrorMessages.friendly_message(reason)
) )
{:noreply, socket} {:noreply, socket}
+67
View File
@@ -0,0 +1,67 @@
defmodule MusicLibraryWeb.ErrorMessages do
@moduledoc """
Maps internal error terms to user-friendly messages.
Used at call sites that previously showed `inspect(reason)` in toasts or assigns.
Each call site keeps its own contextual prefix (e.g. "Error refreshing cover")
and appends `": " <> friendly_message(reason)` for the reason part.
"""
use Gettext, backend: MusicLibraryWeb.Gettext
# App error atoms
def friendly_message(:cover_not_available), do: gettext("cover art is not available")
def friendly_message(:no_duration), do: gettext("release has no track duration information")
def friendly_message(:medium_not_found), do: gettext("the specified disc was not found")
def friendly_message(:no_session_key), do: gettext("Last.fm session key is not configured")
def friendly_message(:already_collected),
do: gettext("this record is already in your collection")
def friendly_message(:not_found), do: gettext("the resource was not found")
def friendly_message(:no_discogs_data), do: gettext("no Discogs profile available")
def friendly_message(:image_not_found), do: gettext("no image could be found")
def friendly_message(:invalid_parameters), do: gettext("invalid parameters were provided")
def friendly_message(:download_failed), do: gettext("the download failed")
# Last.fm API error atoms
def friendly_message(:authentication_failed), do: gettext("Last.fm authentication failed")
def friendly_message(:invalid_session_key),
do: gettext("Last.fm session has expired, please reconnect")
def friendly_message(:invalid_api_key), do: gettext("Last.fm API key is invalid")
def friendly_message(:suspended_api_key), do: gettext("Last.fm API key has been suspended")
def friendly_message(:rate_limit_exceeded),
do: gettext("rate limit exceeded, please try again later")
def friendly_message(:service_offline),
do: gettext("service is currently offline, please try again later")
def friendly_message(:transient_error),
do: gettext("a temporary error occurred, please try again")
def friendly_message(:operation_failed), do: gettext("the operation failed, please try again")
def friendly_message(:invalid_resource),
do: gettext("the requested resource does not exist")
# Structured errors
def friendly_message(%Req.TransportError{reason: :timeout}),
do: gettext("the request timed out, please try again")
def friendly_message(%Req.TransportError{reason: :econnrefused}),
do: gettext("the service is not reachable")
def friendly_message(%Req.TransportError{reason: :nxdomain}),
do: gettext("the service could not be found")
def friendly_message(%Req.TransportError{}),
do: gettext("a connection error occurred, please try again")
def friendly_message(%Ecto.Changeset{}), do: gettext("validation failed")
# Fallback
def friendly_message(_), do: gettext("something went wrong, please try again")
end
+17 -4
View File
@@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.ArtistLive.Form do
alias MusicLibrary.Artists alias MusicLibrary.Artists
alias MusicLibrary.Assets alias MusicLibrary.Assets
alias MusicLibrary.Assets.Image alias MusicLibrary.Assets.Image
alias MusicLibraryWeb.ErrorMessages
@impl true @impl true
def mount(socket) do def mount(socket) do
@@ -249,14 +250,20 @@ defmodule MusicLibraryWeb.ArtistLive.Form do
def handle_async(:image_search, {:ok, {:error, reason}}, socket) do def handle_async(:image_search, {:ok, {:error, reason}}, socket) do
{:noreply, {:noreply,
socket socket
|> assign(:image_search_error, "Search failed: #{inspect(reason)}") |> assign(
:image_search_error,
gettext("Search failed") <> ": " <> ErrorMessages.friendly_message(reason)
)
|> assign(:image_search_loading, false)} |> assign(:image_search_loading, false)}
end end
def handle_async(:image_search, {:exit, reason}, socket) do def handle_async(:image_search, {:exit, reason}, socket) do
{:noreply, {:noreply,
socket socket
|> assign(:image_search_error, "Search failed: #{inspect(reason)}") |> assign(
:image_search_error,
gettext("Search failed") <> ": " <> ErrorMessages.friendly_message(reason)
)
|> assign(:image_search_loading, false)} |> assign(:image_search_loading, false)}
end end
@@ -283,14 +290,20 @@ defmodule MusicLibraryWeb.ArtistLive.Form do
def handle_async(:image_download, {:ok, {:error, reason}}, socket) do def handle_async(:image_download, {:ok, {:error, reason}}, socket) do
{:noreply, {:noreply,
socket socket
|> assign(:image_search_error, "Download failed: #{inspect(reason)}") |> assign(
:image_search_error,
gettext("Download failed") <> ": " <> ErrorMessages.friendly_message(reason)
)
|> assign(:image_search_loading, false)} |> assign(:image_search_loading, false)}
end end
def handle_async(:image_download, {:exit, reason}, socket) do def handle_async(:image_download, {:exit, reason}, socket) do
{:noreply, {:noreply,
socket socket
|> assign(:image_search_error, "Download failed: #{inspect(reason)}") |> assign(
:image_search_error,
gettext("Download failed") <> ": " <> ErrorMessages.friendly_message(reason)
)
|> assign(:image_search_loading, false)} |> assign(:image_search_loading, false)}
end end
+10 -5
View File
@@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
alias MusicLibrary.{Artists, Records} alias MusicLibrary.{Artists, Records}
alias MusicLibrary.Artists.ArtistInfo alias MusicLibrary.Artists.ArtistInfo
alias MusicLibraryWeb.ErrorMessages
attr :country, :map, required: true attr :country, :map, required: true
@@ -544,7 +545,8 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error refreshing artist info") <> "," <> inspect(reason) gettext("Error refreshing artist info") <>
": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -563,7 +565,8 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error refreshing Wikipedia data") <> "," <> inspect(reason) gettext("Error refreshing Wikipedia data") <>
": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -585,7 +588,8 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error refreshing Last.fm data") <> "," <> inspect(reason) gettext("Error refreshing Last.fm data") <>
": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -603,7 +607,8 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error refreshing artist image") <> "," <> inspect(reason) gettext("Error refreshing artist image") <>
": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -624,7 +629,7 @@ defmodule MusicLibraryWeb.ArtistLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error importing record") <> "," <> inspect(changeset.errors) gettext("Error importing record") <> ": " <> ErrorMessages.friendly_message(changeset)
)} )}
end end
end end
@@ -9,6 +9,7 @@ defmodule MusicLibraryWeb.CollectionLive.Index do
alias MusicLibrary.Collection alias MusicLibrary.Collection
alias MusicLibrary.Records alias MusicLibrary.Records
alias MusicLibraryWeb.CollectionLive.Show alias MusicLibraryWeb.CollectionLive.Show
alias MusicLibraryWeb.ErrorMessages
@default_records_list_params %{ @default_records_list_params %{
query: "", query: "",
@@ -272,20 +273,14 @@ defmodule MusicLibraryWeb.CollectionLive.Index do
|> put_toast(:info, gettext("Record imported successfully")) |> put_toast(:info, gettext("Record imported successfully"))
|> push_navigate(to: ~p"/collection/#{record.id}")} |> push_navigate(to: ~p"/collection/#{record.id}")}
{:error, %Ecto.Changeset{} = changeset} -> {:error, reason} ->
{:noreply, {:noreply,
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error importing record") <> "," <> inspect(changeset.errors) gettext("Error importing record") <> ": " <> ErrorMessages.friendly_message(reason)
) )
|> push_patch(to: ~p"/collection")} |> push_patch(to: ~p"/collection")}
{:error, reason} ->
{:noreply,
socket
|> put_toast(:error, gettext("Error importing record") <> "," <> inspect(reason))
|> push_patch(to: ~p"/collection")}
end end
end end
@@ -21,6 +21,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
alias MusicLibrary.{Records, RecordSets, ScrobbleActivity} alias MusicLibrary.{Records, RecordSets, ScrobbleActivity}
alias MusicLibrary.Records.Similarity alias MusicLibrary.Records.Similarity
alias MusicLibraryWeb.ErrorMessages
alias Phoenix.LiveView.JS alias Phoenix.LiveView.JS
@impl true @impl true
@@ -370,7 +371,8 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error refreshing MusicBrainz data") <> "," <> inspect(reason) gettext("Error refreshing MusicBrainz data") <>
": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -389,7 +391,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error") <> "," <> inspect(reason) gettext("Error") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -409,7 +411,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error refreshing cover") <> "," <> inspect(reason) gettext("Error refreshing cover") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -429,7 +431,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error extracting colors") <> ": " <> inspect(reason) gettext("Error extracting colors") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -448,7 +450,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error") <> "," <> inspect(reason) gettext("Error") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do
alias MusicLibrary.Artists alias MusicLibrary.Artists
alias MusicLibrary.Maintenance alias MusicLibrary.Maintenance
alias MusicLibrary.Records alias MusicLibrary.Records
alias MusicLibraryWeb.ErrorMessages
@poll_interval 2_000 @poll_interval 2_000
@@ -268,7 +269,10 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do
{:noreply, {:noreply,
socket socket
|> put_toast(:error, "Database vacuum failed: #{inspect(reason)}.")} |> put_toast(
:error,
gettext("Database vacuum failed") <> ": " <> ErrorMessages.friendly_message(reason)
)}
end end
end end
@@ -284,7 +288,10 @@ defmodule MusicLibraryWeb.MaintenanceLive.Index do
{:noreply, {:noreply,
socket socket
|> put_toast(:error, "Database optimize failed: #{inspect(reason)}.")} |> put_toast(
:error,
gettext("Database optimize failed") <> ": " <> ErrorMessages.friendly_message(reason)
)}
end end
end end
end end
@@ -5,6 +5,7 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
import MusicLibraryWeb.RecordComponents, only: [country_label: 1] import MusicLibraryWeb.RecordComponents, only: [country_label: 1]
alias MusicLibrary.ScrobbleActivity alias MusicLibrary.ScrobbleActivity
alias MusicLibraryWeb.ErrorMessages
@impl true @impl true
def render(assigns) do def render(assigns) do
@@ -156,7 +157,7 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error scrobbling release") <> "," <> inspect(reason) gettext("Error scrobbling release") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -177,7 +178,7 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error scrobbling disc") <> "," <> inspect(reason) gettext("Error scrobbling disc") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -205,7 +206,8 @@ defmodule MusicLibraryWeb.ScrobbleLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error scrobbling selected tracks") <> "," <> inspect(reason) gettext("Error scrobbling selected tracks") <>
": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -9,6 +9,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do
alias MusicLibrary.Assets.Transform alias MusicLibrary.Assets.Transform
alias MusicLibrary.{Collection, ListeningStats, Records, Wishlist} alias MusicLibrary.{Collection, ListeningStats, Records, Wishlist}
alias MusicLibraryWeb.ErrorMessages
alias MusicLibraryWeb.StatsLive.{TopAlbums, TopArtists} alias MusicLibraryWeb.StatsLive.{TopAlbums, TopArtists}
@impl true @impl true
@@ -392,18 +393,13 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|> put_toast(:info, gettext("Record wishlisted successfully")) |> put_toast(:info, gettext("Record wishlisted successfully"))
|> push_navigate(to: ~p"/wishlist/#{record.id}")} |> push_navigate(to: ~p"/wishlist/#{record.id}")}
{:error, %Ecto.Changeset{} = changeset} -> {:error, reason} ->
{:noreply, {:noreply,
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error wishlisting record") <> "," <> inspect(changeset.errors) gettext("Error wishlisting record") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
{:error, reason} ->
{:noreply,
socket
|> put_toast(:error, gettext("Error wishlisting record") <> "," <> inspect(reason))}
end end
end end
@@ -7,6 +7,7 @@ defmodule MusicLibraryWeb.WishlistLive.Index do
alias MusicLibrary.Records alias MusicLibrary.Records
alias MusicLibrary.Wishlist alias MusicLibrary.Wishlist
alias MusicLibraryWeb.ErrorMessages
alias MusicLibraryWeb.WishlistLive.Show alias MusicLibraryWeb.WishlistLive.Show
@default_records_list_params %{ @default_records_list_params %{
@@ -242,20 +243,14 @@ defmodule MusicLibraryWeb.WishlistLive.Index do
|> put_toast(:info, gettext("Record wishlisted successfully")) |> put_toast(:info, gettext("Record wishlisted successfully"))
|> push_navigate(to: ~p"/wishlist/#{record.id}")} |> push_navigate(to: ~p"/wishlist/#{record.id}")}
{:error, %Ecto.Changeset{} = changeset} -> {:error, reason} ->
{:noreply, {:noreply,
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error wishlisting record") <> "," <> inspect(changeset.errors) gettext("Error wishlisting record") <> ": " <> ErrorMessages.friendly_message(reason)
) )
|> push_patch(to: ~p"/wishlist")} |> push_patch(to: ~p"/wishlist")}
{:error, reason} ->
{:noreply,
socket
|> put_toast(:error, gettext("Error wishlisting record") <> "," <> inspect(reason))
|> push_patch(to: ~p"/wishlist")}
end end
end end
@@ -19,6 +19,7 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
alias MusicLibrary.OnlineStoreTemplates alias MusicLibrary.OnlineStoreTemplates
alias MusicLibrary.{Records, RecordSets} alias MusicLibrary.{Records, RecordSets}
alias MusicLibrary.Records.Similarity alias MusicLibrary.Records.Similarity
alias MusicLibraryWeb.ErrorMessages
@impl true @impl true
def render(assigns) do def render(assigns) do
@@ -319,7 +320,8 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error refreshing MusicBrainz data") <> "," <> inspect(reason) gettext("Error refreshing MusicBrainz data") <>
": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -339,7 +341,7 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error refreshing Cover") <> "," <> inspect(reason) gettext("Error refreshing cover") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -358,7 +360,7 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error") <> "," <> inspect(reason) gettext("Error") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
@@ -394,7 +396,7 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
socket socket
|> put_toast( |> put_toast(
:error, :error,
gettext("Error extracting colors") <> ": " <> inspect(reason) gettext("Error extracting colors") <> ": " <> ErrorMessages.friendly_message(reason)
)} )}
end end
end end
+148 -5
View File
@@ -279,12 +279,8 @@ msgstr ""
msgid "Cover refreshed successfully" msgid "Cover refreshed successfully"
msgstr "" msgstr ""
#: lib/music_library_web/live/wishlist_live/show.ex
#, elixir-autogen, elixir-format
msgid "Error refreshing Cover"
msgstr ""
#: lib/music_library_web/live/collection_live/show.ex #: lib/music_library_web/live/collection_live/show.ex
#: lib/music_library_web/live/wishlist_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Error refreshing cover" msgid "Error refreshing cover"
msgstr "" msgstr ""
@@ -2069,3 +2065,150 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Extract colors" msgid "Extract colors"
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format
msgid "Database optimize failed"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format
msgid "Database vacuum failed"
msgstr ""
#: lib/music_library_web/components/record_form.ex
#: lib/music_library_web/live/artist_live/form.ex
#, elixir-autogen, elixir-format
msgid "Download failed"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm API key has been suspended"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm API key is invalid"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm authentication failed"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm session has expired, please reconnect"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm session key is not configured"
msgstr ""
#: lib/music_library_web/components/record_form.ex
#: lib/music_library_web/live/artist_live/form.ex
#, elixir-autogen, elixir-format
msgid "Search failed"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "a connection error occurred, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "a temporary error occurred, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "cover art is not available"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "invalid parameters were provided"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "no Discogs profile available"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "no image could be found"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "rate limit exceeded, please try again later"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "release has no track duration information"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "service is currently offline, please try again later"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "something went wrong, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the download failed"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the operation failed, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the request timed out, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the requested resource does not exist"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the resource was not found"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the service could not be found"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the service is not reachable"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the specified disc was not found"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "this record is already in your collection"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "validation failed"
msgstr ""
+148 -5
View File
@@ -279,12 +279,8 @@ msgstr ""
msgid "Cover refreshed successfully" msgid "Cover refreshed successfully"
msgstr "" msgstr ""
#: lib/music_library_web/live/wishlist_live/show.ex
#, elixir-autogen, elixir-format
msgid "Error refreshing Cover"
msgstr ""
#: lib/music_library_web/live/collection_live/show.ex #: lib/music_library_web/live/collection_live/show.ex
#: lib/music_library_web/live/wishlist_live/show.ex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Error refreshing cover" msgid "Error refreshing cover"
msgstr "" msgstr ""
@@ -2069,3 +2065,150 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Extract colors" msgid "Extract colors"
msgstr "" msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Database optimize failed"
msgstr ""
#: lib/music_library_web/live/maintenance_live/index.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Database vacuum failed"
msgstr ""
#: lib/music_library_web/components/record_form.ex
#: lib/music_library_web/live/artist_live/form.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Download failed"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm API key has been suspended"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm API key is invalid"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm authentication failed"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm session has expired, please reconnect"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "Last.fm session key is not configured"
msgstr ""
#: lib/music_library_web/components/record_form.ex
#: lib/music_library_web/live/artist_live/form.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "Search failed"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "a connection error occurred, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "a temporary error occurred, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "cover art is not available"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "invalid parameters were provided"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "no Discogs profile available"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "no image could be found"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "rate limit exceeded, please try again later"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "release has no track duration information"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "service is currently offline, please try again later"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format, fuzzy
msgid "something went wrong, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the download failed"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the operation failed, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the request timed out, please try again"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the requested resource does not exist"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the resource was not found"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the service could not be found"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the service is not reachable"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "the specified disc was not found"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "this record is already in your collection"
msgstr ""
#: lib/music_library_web/error_messages.ex
#, elixir-autogen, elixir-format
msgid "validation failed"
msgstr ""
@@ -0,0 +1,90 @@
defmodule MusicLibraryWeb.ErrorMessagesTest do
use ExUnit.Case, async: true
alias MusicLibraryWeb.ErrorMessages
describe "friendly_message/1" do
test "maps known app error atoms" do
assert ErrorMessages.friendly_message(:cover_not_available) == "cover art is not available"
assert ErrorMessages.friendly_message(:no_duration) ==
"release has no track duration information"
assert ErrorMessages.friendly_message(:medium_not_found) ==
"the specified disc was not found"
assert ErrorMessages.friendly_message(:no_session_key) ==
"Last.fm session key is not configured"
assert ErrorMessages.friendly_message(:already_collected) ==
"this record is already in your collection"
assert ErrorMessages.friendly_message(:not_found) == "the resource was not found"
assert ErrorMessages.friendly_message(:no_discogs_data) == "no Discogs profile available"
assert ErrorMessages.friendly_message(:image_not_found) == "no image could be found"
assert ErrorMessages.friendly_message(:invalid_parameters) ==
"invalid parameters were provided"
assert ErrorMessages.friendly_message(:download_failed) == "the download failed"
end
test "maps Last.fm API error atoms" do
assert ErrorMessages.friendly_message(:authentication_failed) ==
"Last.fm authentication failed"
assert ErrorMessages.friendly_message(:invalid_session_key) ==
"Last.fm session has expired, please reconnect"
assert ErrorMessages.friendly_message(:invalid_api_key) == "Last.fm API key is invalid"
assert ErrorMessages.friendly_message(:suspended_api_key) ==
"Last.fm API key has been suspended"
assert ErrorMessages.friendly_message(:rate_limit_exceeded) ==
"rate limit exceeded, please try again later"
assert ErrorMessages.friendly_message(:service_offline) ==
"service is currently offline, please try again later"
assert ErrorMessages.friendly_message(:transient_error) ==
"a temporary error occurred, please try again"
assert ErrorMessages.friendly_message(:operation_failed) ==
"the operation failed, please try again"
assert ErrorMessages.friendly_message(:invalid_resource) ==
"the requested resource does not exist"
end
test "maps Req.TransportError with common reasons" do
assert ErrorMessages.friendly_message(%Req.TransportError{reason: :timeout}) ==
"the request timed out, please try again"
assert ErrorMessages.friendly_message(%Req.TransportError{reason: :econnrefused}) ==
"the service is not reachable"
assert ErrorMessages.friendly_message(%Req.TransportError{reason: :nxdomain}) ==
"the service could not be found"
assert ErrorMessages.friendly_message(%Req.TransportError{reason: :closed}) ==
"a connection error occurred, please try again"
end
test "maps Ecto.Changeset" do
changeset = Ecto.Changeset.change(%MusicLibrary.Records.Record{})
assert ErrorMessages.friendly_message(changeset) == "validation failed"
end
test "returns fallback for unknown terms" do
assert ErrorMessages.friendly_message(:unknown_error) ==
"something went wrong, please try again"
assert ErrorMessages.friendly_message("some string") ==
"something went wrong, please try again"
assert ErrorMessages.friendly_message({:complex, :term}) ==
"something went wrong, please try again"
end
end
end