• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / supavisor / 21979016732

13 Feb 2026 07:51AM UTC coverage: 75.945% (+2.0%) from 73.948%
21979016732

Pull #841

github

web-flow
Merge d6420ab5a into bdcbf2f18
Pull Request #841: Improve errors, add error codes

141 of 201 new or added lines in 42 files covered. (70.15%)

8 existing lines in 3 files now uncovered.

2090 of 2752 relevant lines covered (75.94%)

4456.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.67
/lib/supavisor/client_handler/error.ex
1
defmodule Supavisor.ClientHandler.Error do
2
  @moduledoc """
3
  Error handling and message formatting for ClientHandler.
4
  """
5

6
  alias Supavisor.{HandlerHelpers, Monitoring.Telem, Protocol.Server}
7

8
  require Supavisor.Protocol.PreparedStatements, as: PreparedStatements
9
  require Logger
10

11
  @type context :: :handshake | :authenticated
12

13
  @doc """
14
  Handles error by logging, sending appropriate error message to the client socket,
15
  recording telemetry failure, and returning gen_statem termination action.
16

17
  Context values:
18
  - `:handshake` - Error during connection setup/auth (records client_join(:fail) telemetry, no ReadyForQuery)
19
  - `:authenticated` - Error after successful auth (no telemetry, sends ReadyForQuery)
20
  """
21
  @spec terminate_with_error(map(), Exception.t(), context()) ::
22
          :gen_statem.handle_event_result()
23
  def terminate_with_error(data, exception, context)
24
      when context in [:handshake, :authenticated] do
25
    error_actions = process(exception, context)
319✔
26
    message = Map.get(error_actions, :error)
319✔
27
    log_message = Map.get(error_actions, :log_message)
319✔
28
    log_level = Map.get(error_actions, :log_level, :error)
319✔
29
    send_ready_for_query = Map.get(error_actions, :send_ready_for_query, false)
319✔
30
    auth_error = Map.get(error_actions, :auth_error, false)
319✔
31

32
    if log_message do
319✔
33
      Logger.log(log_level, "ClientHandler: #{log_message}", auth_error: auth_error)
319✔
34
    end
35

36
    # Only send message if one exists (some errors like socket closed can't send)
37
    if message do
319✔
38
      if send_ready_for_query do
36✔
39
        HandlerHelpers.sock_send(data.sock, [message, Server.ready_for_query()])
7✔
40
      else
41
        HandlerHelpers.sock_send(data.sock, message)
29✔
42
      end
43
    end
44

45
    # Record telemetry failure only during handshake phase
46
    if context == :handshake do
319✔
47
      Telem.client_join(:fail, data.id)
41✔
48
    end
49

50
    {:stop, :normal}
51
  end
52

53
  @spec process(term(), term()) :: map()
54
  defp process(e, stage) when is_exception(e) do
55
    error =
319✔
56
      case e.__struct__.postgres_error(e) do
319✔
57
        nil -> nil
283✔
58
        postgres_error -> Server.encode_error_message(postgres_error)
36✔
59
      end
60

61
    %{
319✔
62
      error: error,
63
      log_message: e.__struct__.log_message(e),
319✔
64
      log_level: e.__struct__.log_level(e),
319✔
65
      # It's very important for the protocol implementation that we send ReadyForQuery after
66
      # fatal errors in authenticated connections. In non authneticated connections, we should
67
      # close without sending ReadyForQuery
68
      send_ready_for_query: stage == :authenticated,
69
      auth_error: e.__struct__.is_auth_error(e)
319✔
70
    }
71
  end
72

73
  defp process(error, context) do
NEW
74
    message = "Internal error (#{context}): #{inspect(error)}"
×
75

76
    %{
×
77
      error: Server.error_message("XX000", message),
78
      log_message: message
79
    }
80
  end
81
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc