Repository: openlawlibrary/pygls

Issue:
If the request handler throws an exception, respond with Error
An endpoint request handler may throw an exception, e.g.

```
:: --> anakin textDocument/hover(3): {'position': {'character': 30, 'line': 37}, 'textDocument': {'uri': 'file:///home/raoul/.config/sublime-text-3/Packages/LSP/plugin/core/session_view.py'}}
anakin: ERROR:pygls.protocol:Failed to handle request 3 textDocument/hover Object(position=Object(character=30, line=37), textDocument=Object(uri='file:///home/raoul/.config/sublime-text-3/Packages/LSP/plugin/core/session_view.py'))
:: <<< anakin 3: AttributeError: 'NoneType' object has no attribute 'type'
anakin: Traceback (most recent call last):
anakin: File "/home/raoul/.local/lib/python3.7/site-packages/pygls/protocol.py", line 325, in _handle_request
anakin: self._execute_request(msg_id, handler, params)
anakin: File "/home/raoul/.local/lib/python3.7/site-packages/pygls/protocol.py", line 250, in _execute_request
anakin: self._send_response(msg_id, handler(params))
anakin: File "/home/raoul/.local/lib/python3.7/site-packages/anakinls/server.py", line 498, in hover
anakin: names = fn(params.position.line + 1, params.position.character)
anakin: File "/home/raoul/.local/lib/python3.7/site-packages/jedi/api/helpers.py", line 465, in wrapper
anakin: return func(self, line, column, *args, **kwargs)
anakin: File "/home/raoul/.local/lib/python3.7/site-packages/jedi/api/__init__.py", line 475, in help
anakin: if leaf.type in ('keyword', 'operator', 'error_leaf'):
anakin: AttributeError: 'NoneType' object has no attribute 'type'
```

If something failed to handle a request, this library should return a `ResponseError` with error code `InternalError`, and hopefully some descriptive message. Right now, the stacktrace is dumped as a string, but that's not a valid response.

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index efc4a7c..945aeed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,9 +21,11 @@ and this project adheres to [Semantic Versioning][semver].
 ### Fixed
 
 - Periodically check client pid and exit server ([#149])
+- Fix server handling of client errors ([#141])
 
-[#139]: https://github.com/openlawlibrary/pygls/pull/139
 [#149]: https://github.com/openlawlibrary/pygls/pull/149
+[#141]: https://github.com/openlawlibrary/pygls/pull/141
+[#139]: https://github.com/openlawlibrary/pygls/pull/139
 
 ## [0.9.1] - 09/29/2020
 
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 0aef6f8..89571f6 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -2,6 +2,7 @@
 
 - [@augb](https://github.com/augb)
 - [Daniel Elero](https://github.com/danixeee)
+- [Daniel Miller](https://github.com/millerdev)
 - [DeathAxe](https://github.com/deathaxe)
 - [Denis Loginov](https://github.com/dinvlad)
 - [Jérome Perrin](https://github.com/perrinjerome)
diff --git a/pygls/exceptions.py b/pygls/exceptions.py
index e906296..503f4ea 100644
--- a/pygls/exceptions.py
+++ b/pygls/exceptions.py
@@ -23,8 +23,9 @@ class JsonRpcException(Exception):
     """A class used as a base class for json rpc exceptions."""
 
     def __init__(self, message=None, code=None, data=None):
-        super().__init__()
-        self.message = message or getattr(self.__class__, 'MESSAGE')
+        message = message or getattr(self.__class__, 'MESSAGE')
+        super().__init__(message)
+        self.message = message
         self.code = code or getattr(self.__class__, 'CODE')
         self.data = data
 
diff --git a/pygls/protocol.py b/pygls/protocol.py
index 6fb1443..0e1d481 100644
--- a/pygls/protocol.py
+++ b/pygls/protocol.py
@@ -361,9 +361,9 @@ class JsonRPCProtocol(asyncio.Protocol):
         if error is not None:
             logger.debug('Received error response to message "%s": %s', msg_id, error)
             future.set_exception(JsonRpcException.from_dict(error))
-
-        logger.debug('Received result for message "%s": %s', msg_id, result)
-        future.set_result(result)
+        else:
+            logger.debug('Received result for message "%s": %s', msg_id, result)
+            future.set_result(result)
 
     def _procedure_handler(self, message):
         """Delegates message to handlers depending on message type."""


Test patch:
diff --git a/tests/test_protocol.py b/tests/test_protocol.py
index 1c83a10..a92d32c 100644
--- a/tests/test_protocol.py
+++ b/tests/test_protocol.py
@@ -15,12 +15,13 @@
 # limitations under the License.                                           #
 ############################################################################
 import json
+from concurrent.futures import Future
 from functools import partial
 from pathlib import Path
 from typing import Optional
 
 import pytest
-from pygls.exceptions import JsonRpcInvalidParams
+from pygls.exceptions import JsonRpcException, JsonRpcInvalidParams
 from pygls.lsp import Model, get_method_params_type
 from pygls.lsp.types import ClientCapabilities, InitializeParams, InitializeResult
 from pygls.protocol import JsonRPCNotification, JsonRPCRequestMessage, JsonRPCResponseMessage
@@ -230,6 +231,28 @@ def test_data_received_multi_message_should_handle_messages(client_server):
     server.lsp.data_received(data)
 
 
+def test_data_received_error_should_raise_jsonrpc_error(client_server):
+    _, server = client_server
+    body = json.dumps({
+        "jsonrpc": "2.0",
+        "id": "err",
+        "error": {
+            "code": -1,
+            "message": "message for you sir",
+        },
+    })
+    message = '\r\n'.join([
+        'Content-Length: ' + str(len(body)),
+        'Content-Type: application/vscode-jsonrpc; charset=utf-8',
+        '',
+        body,
+    ]).encode("utf-8")
+    future = server.lsp._server_request_futures["err"] = Future()
+    server.lsp.data_received(message)
+    with pytest.raises(JsonRpcException, match="message for you sir"):
+        future.result()
+
+
 def test_initialize_should_return_server_capabilities(client_server):
     _, server = client_server
     params = InitializeParams(