diff --git a/apps/debug_adapter/lib/debug_adapter/error_dictionary.ex b/apps/debug_adapter/lib/debug_adapter/error_dictionary.ex new file mode 100644 index 000000000..d83179be2 --- /dev/null +++ b/apps/debug_adapter/lib/debug_adapter/error_dictionary.ex @@ -0,0 +1,23 @@ +defmodule ElixirLS.DebugAdapter.ErrorDictionary do + @moduledoc """ + Provides mapping from error names to unique integer codes for DAP error messages. + """ + + @codes %{ + "internalServerError" => 1, + "cancelled" => 2, + "invalidRequest" => 3, + "launchError" => 4, + "attachError" => 5, + "invalidArgument" => 6, + "evaluateError" => 7, + "argumentError" => 8, + "runtimeError" => 9, + "notSupported" => 10 + } + + @spec code(String.t()) :: integer() + def code(name) do + Map.fetch!(@codes, name) + end +end diff --git a/apps/debug_adapter/lib/debug_adapter/output.ex b/apps/debug_adapter/lib/debug_adapter/output.ex index f102e90e3..12bf5e005 100644 --- a/apps/debug_adapter/lib/debug_adapter/output.ex +++ b/apps/debug_adapter/lib/debug_adapter/output.ex @@ -24,6 +24,7 @@ defmodule ElixirLS.DebugAdapter.Output do def send_error_response( server \\ __MODULE__, request_packet, + error_code, message, format, variables, @@ -32,8 +33,8 @@ defmodule ElixirLS.DebugAdapter.Output do ) do GenServer.call( server, - {:send_error_response, request_packet, message, format, variables, send_telemetry, - show_user}, + {:send_error_response, request_packet, error_code, message, format, variables, + send_telemetry, show_user}, :infinity ) end @@ -142,8 +143,8 @@ defmodule ElixirLS.DebugAdapter.Output do end def handle_call( - {:send_error_response, request_packet, message, format, variables, send_telemetry, - show_user}, + {:send_error_response, request_packet, error_code, message, format, variables, + send_telemetry, show_user}, _from, seq ) do @@ -159,8 +160,7 @@ defmodule ElixirLS.DebugAdapter.Output do message: message, body: %{ error: %GenDAP.Structures.Message{ - # TODO unique ids - id: 1, + id: error_code, format: format, variables: variables, send_telemetry: send_telemetry, diff --git a/apps/debug_adapter/lib/debug_adapter/protocol.basic.ex b/apps/debug_adapter/lib/debug_adapter/protocol.basic.ex index 8cd68a1d6..8d541fc20 100644 --- a/apps/debug_adapter/lib/debug_adapter/protocol.basic.ex +++ b/apps/debug_adapter/lib/debug_adapter/protocol.basic.ex @@ -58,6 +58,13 @@ defmodule ElixirLS.DebugAdapter.Protocol.Basic do send_telemetry, show_user ) do + message_value = Macro.expand_once(message, __CALLER__) + error_id = + case message_value do + value when is_binary(value) -> ElixirLS.DebugAdapter.ErrorDictionary.code(value) + _ -> quote(do: ElixirLS.DebugAdapter.ErrorDictionary.code(unquote(message))) + end + quote do %{ "type" => "response", @@ -68,7 +75,7 @@ defmodule ElixirLS.DebugAdapter.Protocol.Basic do "message" => unquote(message), "body" => %{ "error" => %{ - "id" => unquote(seq), + "id" => unquote(error_id), "format" => unquote(format), "variables" => unquote(variables), "showUser" => unquote(show_user), diff --git a/apps/debug_adapter/lib/debug_adapter/server.ex b/apps/debug_adapter/lib/debug_adapter/server.ex index 41ecb6bdf..a325d315b 100644 --- a/apps/debug_adapter/lib/debug_adapter/server.ex +++ b/apps/debug_adapter/lib/debug_adapter/server.ex @@ -26,7 +26,8 @@ defmodule ElixirLS.DebugAdapter.Server do ModuleInfoCache, IdManager, VariableRegistry, - ThreadRegistry + ThreadRegistry, + ErrorDictionary } alias ElixirLS.DebugAdapter.Stacktrace.Frame @@ -346,6 +347,7 @@ defmodule ElixirLS.DebugAdapter.Server do {:error, e = %ServerError{}} -> Output.send_error_response( packet, + ErrorDictionary.code(e.message), e.message, e.format, e.variables, @@ -497,6 +499,7 @@ defmodule ElixirLS.DebugAdapter.Server do e in ServerError -> Output.send_error_response( packet, + ErrorDictionary.code(e.message), e.message, e.format, e.variables, @@ -524,7 +527,17 @@ defmodule ElixirLS.DebugAdapter.Server do message = Exception.format(kind, payload, stacktrace) Output.debugger_console(message) - Output.send_error_response(packet, "internalServerError", message, %{}, true, false) + + Output.send_error_response( + packet, + ErrorDictionary.code("internalServerError"), + "internalServerError", + message, + %{}, + true, + false + ) + {:noreply, state} end end @@ -641,6 +654,7 @@ defmodule ElixirLS.DebugAdapter.Server do Output.send_error_response( packet, + ErrorDictionary.code("internalServerError"), "internalServerError", "Request handler exited with reason #{Exception.format_exit(reason)}", %{}, @@ -788,7 +802,16 @@ defmodule ElixirLS.DebugAdapter.Server do # flush as we are not interested in :DOWN message anymore Process.demonitor(ref, [:flush]) Process.exit(pid, :cancelled) - Output.send_error_response(packet, "cancelled", "cancelled", %{}, false, false) + + Output.send_error_response( + packet, + ErrorDictionary.code("cancelled"), + "cancelled", + "cancelled", + %{}, + false, + false + ) # send progressEnd if cancelling a progress updated_progresses = diff --git a/apps/debug_adapter/test/output_test.exs b/apps/debug_adapter/test/output_test.exs new file mode 100644 index 000000000..85d8c3dc2 --- /dev/null +++ b/apps/debug_adapter/test/output_test.exs @@ -0,0 +1,29 @@ +defmodule ElixirLS.DebugAdapter.OutputTest do + use ExUnit.Case, async: true + import ElixirLS.DebugAdapter.Protocol.Basic + + alias ElixirLS.DebugAdapter.Output + + setup do + {:ok, capture} = ElixirLS.Utils.PacketCapture.start_link(self()) + {:ok, output} = Output.start(:output_test) + Process.group_leader(output, capture) + + on_exit(fn -> + if Process.alive?(output), do: GenServer.stop(output) + end) + + {:ok, %{output: output}} + end + + test "error response uses provided id", %{output: output} do + req = request(1, "cmd") + Output.send_error_response(output, req, 42, "err", "fmt", %{}, false, false) + + assert_receive %{ + "body" => %{"error" => %{"id" => 42}}, + "seq" => 1, + "request_seq" => 1 + } + end +end