From 69bb60d6e22d192c8dbab9a8fc9343939c3e4825 Mon Sep 17 00:00:00 2001 From: Serhii Snozyk Date: Wed, 30 Sep 2026 10:03:35 +0200 Subject: [PATCH] fix: report frames that fail to decode through handle_error/2 `Mint.WebSocket.decode/2` returns a frame that fails to decode as `{:error, reason}` inside the frame list. That element reached `handle_frame/2` as if it were a frame, and `Minch.connect/3` receivers got it as `{:frame, ref, {:error, reason}}`. It now goes to `handle_error/2` as `{:decode_frame, reason}`, the same shape as a payload that fails to decode as a whole. The other frames in the batch are still delivered. --- lib/minch/conn.ex | 4 ++++ test/minch/client_test.exs | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/minch/conn.ex b/lib/minch/conn.ex index 5376eeb..38fd24c 100644 --- a/lib/minch/conn.ex +++ b/lib/minch/conn.ex @@ -181,6 +181,10 @@ defmodule Minch.Conn do defp handle_frame(_frame, %State{conn: nil} = state), do: {:noreply, state} + defp handle_frame({:error, reason}, %State{} = state) do + handle_error({:decode_frame, reason}, state) + end + # the server initiated close defp handle_frame({:close, _, _} = frame, %State{close_frame: nil} = state) do state = state |> stream_frame(frame) |> discard_error() diff --git a/test/minch/client_test.exs b/test/minch/client_test.exs index bef16a4..f1608bf 100644 --- a/test/minch/client_test.exs +++ b/test/minch/client_test.exs @@ -147,6 +147,14 @@ defmodule Minch.ClientTest do assert_receive {:client, :handle_frame, [{:pong, "123"}, _state]} end + test "handle_error/2 is called for a frame that fails to decode", ctx do + assert_receive {:client, :handle_connect, _} + Server.send_frame(ctx.server, [{:text, <<0xFF>>}, {:text, "a"}]) + assert_receive {:client, :handle_error, [{:decode_frame, {:invalid_utf8, <<0xFF>>}}, _]} + assert_receive {:client, :handle_frame, [{:text, "a"}, _state]} + refute_received {:client, :handle_frame, [{:error, _}, _state]} + end + test "handle_frame/2 is called with frames received before a close", ctx do assert_receive {:client, :handle_connect, _} # suspended so the frames and the socket close land in the mailbox together